From 04c41cb672a25878eec06821bda7801f89a70481 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 12 Oct 2010 15:34:32 +0200 Subject: [PATCH] shift config initialization to own "config" plugin --HG-- branch : trunk --- doc/customize.txt | 17 +- pytest/__init__.py | 2 - pytest/_core.py | 427 +++----------------------------------- pytest/hookspec.py | 11 +- pytest/plugin/config.py | 395 +++++++++++++++++++++++++++++++++++ pytest/plugin/pytester.py | 11 +- pytest/plugin/session.py | 2 +- testing/test_conftest.py | 2 +- testing/test_parseopt.py | 2 +- 9 files changed, 440 insertions(+), 429 deletions(-) create mode 100644 pytest/plugin/config.py diff --git a/doc/customize.txt b/doc/customize.txt index eaedf5e64..e94efed57 100644 --- a/doc/customize.txt +++ b/doc/customize.txt @@ -114,12 +114,10 @@ py.test loads plugin modules at tool startup in the following way: * by recursively loading all plugins specified by the ``pytest_plugins`` variable in a ``conftest.py`` file -Specifying plugins in a test module or plugin ------------------------------------------------ +Requiring/Loading plugins in a test module or plugin +------------------------------------------------------------- -You can specify plugins in a test module or a plugin like this: - -.. sourcecode:: python +You can specify plugins in a test module or a plugin like this:: pytest_plugins = "name1", "name2", @@ -138,11 +136,7 @@ The purpose of ``conftest.py`` files is to allow `project-specific test configuration`_. They thus make for a good place to implement project-specific test related features through hooks. For example you may set the `collect_ignore`_ variable depending on a command line option -by defining the following hook in a ``conftest.py`` file: - -.. _`exclude-file-example`: - -.. sourcecode:: python +by defining the following hook in a ``conftest.py`` file:: # ./conftest.py in your root or package dir collect_ignore = ['hello', 'test_world.py'] @@ -223,6 +217,7 @@ initialisation, command line and configuration hooks .. currentmodule:: pytest.hookspec +.. autofunction:: pytest_cmdline_parse .. autofunction:: pytest_namespace .. autofunction:: pytest_addoption .. autofunction:: pytest_cmdline_main @@ -340,7 +335,7 @@ name. Given a filesystem ``fspath`` it is constructed as follows: Reference of important objects involved in hooks =========================================================== -.. autoclass:: pytest._core.Config +.. autoclass:: pytest.plugin.config.Config :members: .. autoclass:: pytest.plugin.session.Item diff --git a/pytest/__init__.py b/pytest/__init__.py index c26945382..72d7cbc1b 100644 --- a/pytest/__init__.py +++ b/pytest/__init__.py @@ -10,8 +10,6 @@ __version__ = "2.0.0dev0" __all__ = ['config', 'cmdline'] from pytest import _core as cmdline -from pytest._core import Config -config = Config() def __main__(): raise SystemExit(cmdline.main()) diff --git a/pytest/_core.py b/pytest/_core.py index 650aa5b1c..42162fb38 100644 --- a/pytest/_core.py +++ b/pytest/_core.py @@ -2,410 +2,10 @@ import py import sys, os default_plugins = ( - "session terminal python runner pdb capture mark skipping tmpdir monkeypatch " - "recwarn pastebin unittest helpconfig nose assertion genscript " + "config session terminal python runner pdb capture mark skipping tmpdir " + "monkeypatch recwarn pastebin unittest helpconfig nose assertion genscript " "junitxml doctest").split() -def main(args=None): - import sys - if args is None: - args = sys.argv[1:] - config = py.test.config - config.parse(args) - try: - exitstatus = config.hook.pytest_cmdline_main(config=config) - except config.Error: - e = sys.exc_info()[1] - sys.stderr.write("ERROR: %s\n" %(e.args[0],)) - exitstatus = 3 - py.test.config = config.__class__() - return exitstatus - -class Parser: - """ Parser for command line arguments. """ - - def __init__(self, usage=None, processopt=None): - self._anonymous = OptionGroup("custom options", parser=self) - self._groups = [] - self._processopt = processopt - self._usage = usage - self.hints = [] - - def processoption(self, option): - if self._processopt: - if option.dest: - self._processopt(option) - - def addnote(self, note): - self._notes.append(note) - - def getgroup(self, name, description="", after=None): - for group in self._groups: - if group.name == name: - return group - group = OptionGroup(name, description, parser=self) - i = 0 - for i, grp in enumerate(self._groups): - if grp.name == after: - break - self._groups.insert(i+1, group) - return group - - addgroup = getgroup - def addgroup(self, name, description=""): - py.log._apiwarn("1.1", "use getgroup() which gets-or-creates") - return self.getgroup(name, description) - - def addoption(self, *opts, **attrs): - """ add an optparse-style option. """ - self._anonymous.addoption(*opts, **attrs) - - def parse(self, args): - optparser = MyOptionParser(self) - groups = self._groups + [self._anonymous] - for group in groups: - if group.options: - desc = group.description or group.name - optgroup = py.std.optparse.OptionGroup(optparser, desc) - optgroup.add_options(group.options) - optparser.add_option_group(optgroup) - return optparser.parse_args([str(x) for x in args]) - - def parse_setoption(self, args, option): - parsedoption, args = self.parse(args) - for name, value in parsedoption.__dict__.items(): - setattr(option, name, value) - return args - - -class OptionGroup: - def __init__(self, name, description="", parser=None): - self.name = name - self.description = description - self.options = [] - self.parser = parser - - def addoption(self, *optnames, **attrs): - """ add an option to this group. """ - option = py.std.optparse.Option(*optnames, **attrs) - self._addoption_instance(option, shortupper=False) - - def _addoption(self, *optnames, **attrs): - option = py.std.optparse.Option(*optnames, **attrs) - self._addoption_instance(option, shortupper=True) - - def _addoption_instance(self, option, shortupper=False): - if not shortupper: - for opt in option._short_opts: - if opt[0] == '-' and opt[1].islower(): - raise ValueError("lowercase shortoptions reserved") - if self.parser: - self.parser.processoption(option) - self.options.append(option) - - -class MyOptionParser(py.std.optparse.OptionParser): - def __init__(self, parser): - self._parser = parser - py.std.optparse.OptionParser.__init__(self, usage=parser._usage) - def format_epilog(self, formatter): - hints = self._parser.hints - if hints: - s = "\n".join(["hint: " + x for x in hints]) + "\n" - s = "\n" + s + "\n" - return s - return "" - -class Conftest(object): - """ the single place for accessing values and interacting - towards conftest modules from py.test objects. - """ - def __init__(self, onimport=None, confcutdir=None): - self._path2confmods = {} - self._onimport = onimport - self._conftestpath2mod = {} - self._confcutdir = confcutdir - self._md5cache = {} - - def setinitial(self, args): - """ try to find a first anchor path for looking up global values - from conftests. This function is usually called _before_ - argument parsing. conftest files may add command line options - and we thus have no completely safe way of determining - which parts of the arguments are actually related to options - and which are file system paths. We just try here to get - bootstrapped ... - """ - current = py.path.local() - opt = '--confcutdir' - for i in range(len(args)): - opt1 = str(args[i]) - if opt1.startswith(opt): - if opt1 == opt: - if len(args) > i: - p = current.join(args[i+1], abs=True) - elif opt1.startswith(opt + "="): - p = current.join(opt1[len(opt)+1:], abs=1) - self._confcutdir = p - break - for arg in args + [current]: - if hasattr(arg, 'startswith') and arg.startswith("--"): - continue - anchor = current.join(arg, abs=1) - if anchor.check(): # we found some file object - self._path2confmods[None] = self.getconftestmodules(anchor) - # let's also consider test* dirs - if anchor.check(dir=1): - for x in anchor.listdir("test*"): - if x.check(dir=1): - self.getconftestmodules(x) - break - else: - assert 0, "no root of filesystem?" - - def getconftestmodules(self, path): - """ return a list of imported conftest modules for the given path. """ - try: - clist = self._path2confmods[path] - except KeyError: - if path is None: - raise ValueError("missing default confest.") - dp = path.dirpath() - clist = [] - if dp != path: - cutdir = self._confcutdir - if cutdir and path != cutdir and not path.relto(cutdir): - pass - else: - conftestpath = path.join("conftest.py") - if conftestpath.check(file=1): - key = conftestpath.computehash() - # XXX logging about conftest loading - if key not in self._md5cache: - clist.append(self.importconftest(conftestpath)) - self._md5cache[key] = conftestpath - else: - # use some kind of logging - print ("WARN: not loading %s" % conftestpath) - clist[:0] = self.getconftestmodules(dp) - self._path2confmods[path] = clist - # be defensive: avoid changes from caller side to - # affect us by always returning a copy of the actual list - return clist[:] - - def rget(self, name, path=None): - mod, value = self.rget_with_confmod(name, path) - return value - - def rget_with_confmod(self, name, path=None): - modules = self.getconftestmodules(path) - modules.reverse() - for mod in modules: - try: - return mod, getattr(mod, name) - except AttributeError: - continue - raise KeyError(name) - - def importconftest(self, conftestpath): - assert conftestpath.check(), conftestpath - try: - return self._conftestpath2mod[conftestpath] - except KeyError: - if not conftestpath.dirpath('__init__.py').check(file=1): - # HACK: we don't want any "globally" imported conftest.py, - # prone to conflicts and subtle problems - modname = str(conftestpath).replace('.', conftestpath.sep) - mod = conftestpath.pyimport(modname=modname) - else: - mod = conftestpath.pyimport() - self._conftestpath2mod[conftestpath] = mod - dirpath = conftestpath.dirpath() - if dirpath in self._path2confmods: - for path, mods in self._path2confmods.items(): - if path and path.relto(dirpath) or path == dirpath: - assert mod not in mods - mods.append(mod) - self._postimport(mod) - return mod - - def _postimport(self, mod): - if self._onimport: - self._onimport(mod) - return mod - - -class CmdOptions(object): - """ holds cmdline options as attributes.""" - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - 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): - #: command line option values - self.option = CmdOptions() - self._parser = Parser( - usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]", - processopt=self._processopt, - ) - #: a pluginmanager instance - self.pluginmanager = PluginManager() - self._conftest = Conftest(onimport=self._onimportconftest) - self.hook = self.pluginmanager.hook - - def _onimportconftest(self, conftestmodule): - self.trace("loaded conftestmodule %r" %(conftestmodule,)) - self.pluginmanager.consider_conftest(conftestmodule) - - def _getmatchingplugins(self, fspath): - allconftests = self._conftest._conftestpath2mod.values() - plugins = [x for x in self.pluginmanager.getplugins() - if x not in allconftests] - plugins += self._conftest.getconftestmodules(fspath) - return plugins - - def trace(self, msg): - if getattr(self.option, 'traceconfig', None): - self.hook.pytest_trace(category="config", msg=msg) - - def _processopt(self, opt): - if hasattr(opt, 'default') and opt.dest: - val = os.environ.get("PYTEST_OPTION_" + opt.dest.upper(), None) - if val is not None: - if opt.type == "int": - val = int(val) - elif opt.type == "long": - val = long(val) - elif opt.type == "float": - val = float(val) - elif not opt.type and opt.action in ("store_true", "store_false"): - val = eval(val) - opt.default = val - else: - name = "option_" + opt.dest - try: - opt.default = self._conftest.rget(name) - except (ValueError, KeyError): - pass - if not hasattr(self.option, opt.dest): - setattr(self.option, opt.dest, opt.default) - - def _preparse(self, args): - self.pluginmanager.consider_setuptools_entrypoints() - self.pluginmanager.consider_env() - self.pluginmanager.consider_preparse(args) - self._conftest.setinitial(args) - self.pluginmanager.do_addoption(self._parser) - - def parse(self, args): - # cmdline arguments into this config object. - # Note that this can only be called once per testing process. - assert not hasattr(self, 'args'), ( - "can only parse cmdline args at most once per Config object") - self._preparse(args) - self._parser.hints.extend(self.pluginmanager._hints) - args = self._parser.parse_setoption(args, self.option) - if not args: - args.append(py.std.os.getcwd()) - self.args = args - - def ensuretemp(self, string, dir=True): - return self.getbasetemp().ensure(string, dir=dir) - - def getbasetemp(self): - if self.basetemp is None: - basetemp = self.option.basetemp - if basetemp: - basetemp = py.path.local(basetemp) - if not basetemp.check(dir=1): - basetemp.mkdir() - else: - basetemp = py.path.local.make_numbered_dir(prefix='pytest-') - self.basetemp = basetemp - return self.basetemp - - def mktemp(self, basename, numbered=False): - basetemp = self.getbasetemp() - if not numbered: - return basetemp.mkdir(basename) - else: - return py.path.local.make_numbered_dir(prefix=basename, - keep=0, rootdir=basetemp, lock_timeout=None) - - def _getcollectclass(self, name, path): - try: - cls = self._conftest.rget(name, path) - except KeyError: - return getattr(py.test.collect, name) - else: - py.log._apiwarn(">1.1", "%r was found in a conftest.py file, " - "use pytest_collect hooks instead." % (cls,)) - return cls - - def getconftest_pathlist(self, name, path=None): - """ return a matching value, which needs to be sequence - of filenames that will be returned as a list of Path - objects (they can be relative to the location - where they were found). - """ - try: - mod, relroots = self._conftest.rget_with_confmod(name, path) - except KeyError: - return None - modpath = py.path.local(mod.__file__).dirpath() - l = [] - for relroot in relroots: - if not isinstance(relroot, py.path.local): - relroot = relroot.replace("/", py.path.local.sep) - relroot = modpath.join(relroot, abs=True) - l.append(relroot) - return l - - def addoptions(self, groupname, *specs): - # add a named group of options to the current testing session. - # This function gets invoked during testing session initialization. - py.log._apiwarn("1.0", - "define pytest_addoptions(parser) to add options", stacklevel=2) - group = self._parser.getgroup(groupname) - for opt in specs: - group._addoption_instance(opt) - return self.option - - def addoption(self, *optnames, **attrs): - return self._parser.addoption(*optnames, **attrs) - - def getvalueorskip(self, name, path=None): - """ return getvalue(name) or call py.test.skip if no value exists. """ - try: - val = self.getvalue(name, path) - if val is None: - raise KeyError(name) - return val - except KeyError: - py.test.skip("no %r value found" %(name,)) - - def getvalue(self, name, path=None): - """ return 'name' value looked up from the 'options' - and then from the first conftest file found up - the path (including the path itself). - if path is None, lookup the value in the initial - conftest modules found during command line parsing. - """ - try: - return getattr(self.option, name) - except AttributeError: - return self._conftest.rget(name, path) - class PluginManager(object): def __init__(self): from pytest import hookspec @@ -530,8 +130,12 @@ class PluginManager(object): mod = importplugin(modname) except KeyboardInterrupt: raise - except py.test.skip.Exception: + except: e = py.std.sys.exc_info()[1] + if not hasattr(py.test, 'skip'): + raise + elif not isinstance(e, py.test.skip.Exception): + raise self._hints.append("skipped plugin %r: %s" %((modname, e.msg))) else: self.register(mod, modname) @@ -771,3 +375,20 @@ class HookCaller: mc = MultiCall(methods, kwargs, firstresult=self.firstresult) return self.hookrelay._performcall(self.name, mc) +pluginmanager = PluginManager() # will trigger default plugin loading + +def main(args=None): + global pluginmanager + if args is None: + args = sys.argv[1:] + hook = pluginmanager.hook + config = hook.pytest_cmdline_parse(pluginmanager=pluginmanager, args=args) + try: + exitstatus = hook.pytest_cmdline_main(config=config) + except config.Error: + e = sys.exc_info()[1] + sys.stderr.write("ERROR: %s\n" %(e.args[0],)) + exitstatus = 3 + pluginmanager = PluginManager() + return exitstatus + diff --git a/pytest/hookspec.py b/pytest/hookspec.py index fdde570ff..d8016e371 100644 --- a/pytest/hookspec.py +++ b/pytest/hookspec.py @@ -8,16 +8,21 @@ def pytest_namespace(): """return dict of name->object to be made globally available in the py.test/pytest namespace. This hook is called before command line options are fully parsed. If you want to provide helper functions - that can interact with a test function invocation, please refer to + that can interact with a test function invocation, please refer to :ref:`funcarg mechanism`. """ +def pytest_cmdline_parse(pluginmanager, args): + """return initialized config object, parsing the specified args. """ +pytest_cmdline_parse.firstresult = True + def pytest_addoption(parser): - """allows to add optparse-style command line options via a call to + """allows to add optparse-style command line options via a call to ``parser.addoption(...)``.""" def pytest_addhooks(pluginmanager): - "allows to add new hooks via pluginmanager.registerhooks(module)" + """called at plugin load time to allow adding new hooks via a call to + pluginmanager.registerhooks(module).""" def pytest_cmdline_main(config): """ called for performing the main command line action. The default diff --git a/pytest/plugin/config.py b/pytest/plugin/config.py new file mode 100644 index 000000000..58501948a --- /dev/null +++ b/pytest/plugin/config.py @@ -0,0 +1,395 @@ + +import py +import os +from pytest._core import PluginManager + +def pytest_cmdline_parse(pluginmanager, args): + config = Config(pluginmanager) + config.parse(args) + return config + +class Parser: + """ Parser for command line arguments. """ + + def __init__(self, usage=None, processopt=None): + self._anonymous = OptionGroup("custom options", parser=self) + self._groups = [] + self._processopt = processopt + self._usage = usage + self.hints = [] + + def processoption(self, option): + if self._processopt: + if option.dest: + self._processopt(option) + + def addnote(self, note): + self._notes.append(note) + + def getgroup(self, name, description="", after=None): + for group in self._groups: + if group.name == name: + return group + group = OptionGroup(name, description, parser=self) + i = 0 + for i, grp in enumerate(self._groups): + if grp.name == after: + break + self._groups.insert(i+1, group) + return group + + addgroup = getgroup + def addgroup(self, name, description=""): + py.log._apiwarn("1.1", "use getgroup() which gets-or-creates") + return self.getgroup(name, description) + + def addoption(self, *opts, **attrs): + """ add an optparse-style option. """ + self._anonymous.addoption(*opts, **attrs) + + def parse(self, args): + optparser = MyOptionParser(self) + groups = self._groups + [self._anonymous] + for group in groups: + if group.options: + desc = group.description or group.name + optgroup = py.std.optparse.OptionGroup(optparser, desc) + optgroup.add_options(group.options) + optparser.add_option_group(optgroup) + return optparser.parse_args([str(x) for x in args]) + + def parse_setoption(self, args, option): + parsedoption, args = self.parse(args) + for name, value in parsedoption.__dict__.items(): + setattr(option, name, value) + return args + + +class OptionGroup: + def __init__(self, name, description="", parser=None): + self.name = name + self.description = description + self.options = [] + self.parser = parser + + def addoption(self, *optnames, **attrs): + """ add an option to this group. """ + option = py.std.optparse.Option(*optnames, **attrs) + self._addoption_instance(option, shortupper=False) + + def _addoption(self, *optnames, **attrs): + option = py.std.optparse.Option(*optnames, **attrs) + self._addoption_instance(option, shortupper=True) + + def _addoption_instance(self, option, shortupper=False): + if not shortupper: + for opt in option._short_opts: + if opt[0] == '-' and opt[1].islower(): + raise ValueError("lowercase shortoptions reserved") + if self.parser: + self.parser.processoption(option) + self.options.append(option) + + +class MyOptionParser(py.std.optparse.OptionParser): + def __init__(self, parser): + self._parser = parser + py.std.optparse.OptionParser.__init__(self, usage=parser._usage) + def format_epilog(self, formatter): + hints = self._parser.hints + if hints: + s = "\n".join(["hint: " + x for x in hints]) + "\n" + s = "\n" + s + "\n" + return s + return "" + +class Conftest(object): + """ the single place for accessing values and interacting + towards conftest modules from py.test objects. + """ + def __init__(self, onimport=None, confcutdir=None): + self._path2confmods = {} + self._onimport = onimport + self._conftestpath2mod = {} + self._confcutdir = confcutdir + self._md5cache = {} + + def setinitial(self, args): + """ try to find a first anchor path for looking up global values + from conftests. This function is usually called _before_ + argument parsing. conftest files may add command line options + and we thus have no completely safe way of determining + which parts of the arguments are actually related to options + and which are file system paths. We just try here to get + bootstrapped ... + """ + current = py.path.local() + opt = '--confcutdir' + for i in range(len(args)): + opt1 = str(args[i]) + if opt1.startswith(opt): + if opt1 == opt: + if len(args) > i: + p = current.join(args[i+1], abs=True) + elif opt1.startswith(opt + "="): + p = current.join(opt1[len(opt)+1:], abs=1) + self._confcutdir = p + break + for arg in args + [current]: + if hasattr(arg, 'startswith') and arg.startswith("--"): + continue + anchor = current.join(arg, abs=1) + if anchor.check(): # we found some file object + self._path2confmods[None] = self.getconftestmodules(anchor) + # let's also consider test* dirs + if anchor.check(dir=1): + for x in anchor.listdir("test*"): + if x.check(dir=1): + self.getconftestmodules(x) + break + else: + assert 0, "no root of filesystem?" + + def getconftestmodules(self, path): + """ return a list of imported conftest modules for the given path. """ + try: + clist = self._path2confmods[path] + except KeyError: + if path is None: + raise ValueError("missing default confest.") + dp = path.dirpath() + clist = [] + if dp != path: + cutdir = self._confcutdir + if cutdir and path != cutdir and not path.relto(cutdir): + pass + else: + conftestpath = path.join("conftest.py") + if conftestpath.check(file=1): + key = conftestpath.computehash() + # XXX logging about conftest loading + if key not in self._md5cache: + clist.append(self.importconftest(conftestpath)) + self._md5cache[key] = conftestpath + else: + # use some kind of logging + print ("WARN: not loading %s" % conftestpath) + clist[:0] = self.getconftestmodules(dp) + self._path2confmods[path] = clist + # be defensive: avoid changes from caller side to + # affect us by always returning a copy of the actual list + return clist[:] + + def rget(self, name, path=None): + mod, value = self.rget_with_confmod(name, path) + return value + + def rget_with_confmod(self, name, path=None): + modules = self.getconftestmodules(path) + modules.reverse() + for mod in modules: + try: + return mod, getattr(mod, name) + except AttributeError: + continue + raise KeyError(name) + + def importconftest(self, conftestpath): + assert conftestpath.check(), conftestpath + try: + return self._conftestpath2mod[conftestpath] + except KeyError: + if not conftestpath.dirpath('__init__.py').check(file=1): + # HACK: we don't want any "globally" imported conftest.py, + # prone to conflicts and subtle problems + modname = str(conftestpath).replace('.', conftestpath.sep) + mod = conftestpath.pyimport(modname=modname) + else: + mod = conftestpath.pyimport() + self._conftestpath2mod[conftestpath] = mod + dirpath = conftestpath.dirpath() + if dirpath in self._path2confmods: + for path, mods in self._path2confmods.items(): + if path and path.relto(dirpath) or path == dirpath: + assert mod not in mods + mods.append(mod) + self._postimport(mod) + return mod + + def _postimport(self, mod): + if self._onimport: + self._onimport(mod) + return mod + + +class CmdOptions(object): + """ holds cmdline options as attributes.""" + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + 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): + #: command line option values + self.option = CmdOptions() + self._parser = Parser( + usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]", + processopt=self._processopt, + ) + #: a pluginmanager instance + self.pluginmanager = pluginmanager or PluginManager() + self._conftest = Conftest(onimport=self._onimportconftest) + self.hook = self.pluginmanager.hook + + def _onimportconftest(self, conftestmodule): + self.trace("loaded conftestmodule %r" %(conftestmodule,)) + self.pluginmanager.consider_conftest(conftestmodule) + + def _getmatchingplugins(self, fspath): + allconftests = self._conftest._conftestpath2mod.values() + plugins = [x for x in self.pluginmanager.getplugins() + if x not in allconftests] + plugins += self._conftest.getconftestmodules(fspath) + return plugins + + def trace(self, msg): + if getattr(self.option, 'traceconfig', None): + self.hook.pytest_trace(category="config", msg=msg) + + def _processopt(self, opt): + if hasattr(opt, 'default') and opt.dest: + val = os.environ.get("PYTEST_OPTION_" + opt.dest.upper(), None) + if val is not None: + if opt.type == "int": + val = int(val) + elif opt.type == "long": + val = long(val) + elif opt.type == "float": + val = float(val) + elif not opt.type and opt.action in ("store_true", "store_false"): + val = eval(val) + opt.default = val + else: + name = "option_" + opt.dest + try: + opt.default = self._conftest.rget(name) + except (ValueError, KeyError): + pass + if not hasattr(self.option, opt.dest): + setattr(self.option, opt.dest, opt.default) + + def _preparse(self, args): + self.pluginmanager.consider_setuptools_entrypoints() + self.pluginmanager.consider_env() + self.pluginmanager.consider_preparse(args) + self._conftest.setinitial(args) + self.pluginmanager.do_addoption(self._parser) + + def parse(self, args): + # cmdline arguments into this config object. + # Note that this can only be called once per testing process. + assert not hasattr(self, 'args'), ( + "can only parse cmdline args at most once per Config object") + self._preparse(args) + self._parser.hints.extend(self.pluginmanager._hints) + args = self._parser.parse_setoption(args, self.option) + if not args: + args.append(py.std.os.getcwd()) + self.args = args + + def ensuretemp(self, string, dir=True): + return self.getbasetemp().ensure(string, dir=dir) + + def getbasetemp(self): + if self.basetemp is None: + basetemp = self.option.basetemp + if basetemp: + basetemp = py.path.local(basetemp) + if not basetemp.check(dir=1): + basetemp.mkdir() + else: + basetemp = py.path.local.make_numbered_dir(prefix='pytest-') + self.basetemp = basetemp + return self.basetemp + + def mktemp(self, basename, numbered=False): + basetemp = self.getbasetemp() + if not numbered: + return basetemp.mkdir(basename) + else: + return py.path.local.make_numbered_dir(prefix=basename, + keep=0, rootdir=basetemp, lock_timeout=None) + + def _getcollectclass(self, name, path): + try: + cls = self._conftest.rget(name, path) + except KeyError: + return getattr(py.test.collect, name) + else: + py.log._apiwarn(">1.1", "%r was found in a conftest.py file, " + "use pytest_collect hooks instead." % (cls,)) + return cls + + def getconftest_pathlist(self, name, path=None): + """ return a matching value, which needs to be sequence + of filenames that will be returned as a list of Path + objects (they can be relative to the location + where they were found). + """ + try: + mod, relroots = self._conftest.rget_with_confmod(name, path) + except KeyError: + return None + modpath = py.path.local(mod.__file__).dirpath() + l = [] + for relroot in relroots: + if not isinstance(relroot, py.path.local): + relroot = relroot.replace("/", py.path.local.sep) + relroot = modpath.join(relroot, abs=True) + l.append(relroot) + return l + + def addoptions(self, groupname, *specs): + # add a named group of options to the current testing session. + # This function gets invoked during testing session initialization. + py.log._apiwarn("1.0", + "define pytest_addoptions(parser) to add options", stacklevel=2) + group = self._parser.getgroup(groupname) + for opt in specs: + group._addoption_instance(opt) + return self.option + + def addoption(self, *optnames, **attrs): + return self._parser.addoption(*optnames, **attrs) + + def getvalueorskip(self, name, path=None): + """ return getvalue(name) or call py.test.skip if no value exists. """ + try: + val = self.getvalue(name, path) + if val is None: + raise KeyError(name) + return val + except KeyError: + py.test.skip("no %r value found" %(name,)) + + def getvalue(self, name, path=None): + """ return 'name' value looked up from the 'options' + and then from the first conftest file found up + the path (including the path itself). + if path is None, lookup the value in the initial + conftest modules found during command line parsing. + """ + try: + return getattr(self.option, name) + except AttributeError: + return self._conftest.rget(name, path) + diff --git a/pytest/plugin/pytester.py b/pytest/plugin/pytester.py index 3cafb2c76..d646f66c8 100644 --- a/pytest/plugin/pytester.py +++ b/pytest/plugin/pytester.py @@ -8,7 +8,6 @@ import re import inspect import time from fnmatch import fnmatch -from pytest._core import Config as pytestConfig from pytest.plugin.session import Collection from py.builtin import print_ from pytest._core import HookRelay @@ -109,7 +108,7 @@ class HookRecorder: from py.builtin import print_ i = 0 entries = list(entries) - backlocals = py.std.sys._getframe(1).f_locals + backlocals = py.std.sys._getframe(1).f_locals while entries: name, check = entries.pop(0) for ind, call in enumerate(self.calls[i:]): @@ -173,6 +172,7 @@ class RunResult: class TmpTestdir: def __init__(self, request): self.request = request + self.Config = request.config.__class__ self._pytest = request.getfuncargvalue("_pytest") # XXX remove duplication with tmpdir plugin basetmp = request.config.ensuretemp("testdir") @@ -195,9 +195,6 @@ class TmpTestdir: def __repr__(self): return "" % (self.tmpdir,) - def Config(self): - return pytestConfig() - def finalize(self): for p in self._syspathremove: py.std.sys.path.remove(p) @@ -341,9 +338,9 @@ class TmpTestdir: """ this is used from tests that want to re-invoke parse(). """ if not args: args = [self.tmpdir] - oldconfig = py.test.config + oldconfig = getattr(py.test, 'config', None) try: - c = py.test.config = pytestConfig() + c = py.test.config = self.Config() c.basetemp = oldconfig.mktemp("reparse", numbered=True) c.parse(args) return c diff --git a/pytest/plugin/session.py b/pytest/plugin/session.py index c052fcad2..5811f1109 100644 --- a/pytest/plugin/session.py +++ b/pytest/plugin/session.py @@ -37,7 +37,7 @@ def pytest_namespace(): File=File, Directory=Directory)) def pytest_configure(config): - # compat + py.test.config = config # compatibiltiy if config.getvalue("exitfirst"): config.option.maxfail = 1 diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 93e4976ed..53f4dfd1f 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -1,5 +1,5 @@ import py -from pytest._core import Conftest +from pytest.plugin.config import Conftest def pytest_generate_tests(metafunc): if "basedir" in metafunc.funcargnames: diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index a7794e3b7..4acbedcd4 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -1,5 +1,5 @@ import py -from pytest import main as parseopt +from pytest.plugin import config as parseopt class TestParser: def test_init(self, capsys):