import py, os from conftesthandle import Conftest from py.__.test import parseopt from py.__.misc.warn import APIWARN # XXX move to Config class basetemp = None def ensuretemp(string, dir=1): """ return temporary directory path with the given string as the trailing part. """ global basetemp if basetemp is None: basetemp = py.path.local.make_numbered_dir(prefix='pytest-') return basetemp.ensure(string, dir=dir) class CmdOptions(object): """ pure container instance for holding cmdline options as attributes. """ def __repr__(self): return "" %(self.__dict__,) class Config(object): """ central bus for dealing with configuration/initialization data. """ Option = py.compat.optparse.Option # deprecated _sessionclass = None def __init__(self, pytestplugins=None, topdir=None): self.option = CmdOptions() self.topdir = topdir self._parser = parseopt.Parser( usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]", processopt=self._processopt, ) if pytestplugins is None: pytestplugins = py.test._PytestPlugins() assert isinstance(pytestplugins, py.test._PytestPlugins) self.bus = pytestplugins.pyplugins self.pytestplugins = pytestplugins self._conftest = Conftest(onimport=self._onimportconftest) self._setupstate = SetupState() def _onimportconftest(self, conftestmodule): self.trace("loaded conftestmodule %r" %(conftestmodule,)) self.pytestplugins.consider_conftest(conftestmodule) def trace(self, msg): if getattr(self.option, 'traceconfig', None): self.bus.notify("trace", "config", 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 = "pytest_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._conftest.setinitial(args) self.pytestplugins.consider_env() self.pytestplugins.do_addoption(self._parser) def parse(self, args): """ parse 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) args = self._parser.parse_setoption(args, self.option) if not args: args.append(py.std.os.getcwd()) self.topdir = gettopdir(args) self.args = [py.path.local(x) for x in args] # config objects are usually pickled across system # barriers but they contain filesystem paths. # upon getstate/setstate we take care to do everything # relative to "topdir". def __getstate__(self): l = [] for path in self.args: path = py.path.local(path) l.append(path.relto(self.topdir)) return l, self.option def __setstate__(self, repr): # warning global side effects: # * registering to py lib plugins # * setting py.test.config self.__init__( pytestplugins=py.test._PytestPlugins(py._com.pyplugins), topdir=py.path.local(), ) # we have to set py.test.config because preparse() # might load conftest files which have # py.test.config.addoptions() lines in them py.test.config = self args, cmdlineopts = repr args = [self.topdir.join(x) for x in args] self.option = cmdlineopts self._preparse(args) self.args = args def getcolitems(self): return [self.getfsnode(arg) for arg in self.args] def getfsnode(self, path): path = py.path.local(path) assert path.check(), "%s: path does not exist" %(path,) # we want our possibly custom collection tree to start at pkgroot pkgpath = path.pypkgpath() if pkgpath is None: pkgpath = path.check(file=1) and path.dirpath() or path Dir = self._conftest.rget("Directory", pkgpath) col = Dir(pkgpath, config=self) return col._getfsnode(path) 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() return [modpath.join(x, abs=True) for x in relroots] def addoptions(self, groupname, *specs): """ add a named group of options to the current testing session. This function gets invoked during testing session initialization. """ APIWARN("1.0", "define plugins to add options", stacklevel=2) group = self._parser.addgroup(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() 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) def setsessionclass(self, cls): if self._sessionclass is not None: raise ValueError("sessionclass already set to: %r" %( self._sessionclass)) self._sessionclass = cls def initsession(self): """ return an initialized session object. """ cls = self._sessionclass if cls is None: from py.__.test.session import Session cls = Session return cls(self) def _reparse(self, args): """ this is used from tests that want to re-invoke parse(). """ #assert args # XXX should not be empty global config_per_process oldconfig = py.test.config try: config_per_process = py.test.config = Config() config_per_process.parse(args) return config_per_process finally: config_per_process = py.test.config = oldconfig def _getcapture(self, path=None): if self.option.nocapture: iocapture = "no" else: iocapture = self.getvalue("conf_iocapture", path=path) if iocapture == "fd": return py.io.StdCaptureFD() elif iocapture == "sys": return py.io.StdCapture() elif iocapture == "no": return py.io.StdCapture(out=False, err=False, in_=False) else: raise ValueError("unknown io capturing: " + iocapture) # # helpers # def checkmarshal(name, value): try: py.std.marshal.dumps(value) except ValueError: raise ValueError("%s=%r is not marshallable" %(name, value)) def gettopdir(args): """ return the top directory for the given paths. if the common base dir resides in a python package parent directory of the root package is returned. """ args = [py.path.local(arg) for arg in args] p = reduce(py.path.local.common, args) assert p, "cannot determine common basedir of %s" %(args,) pkgdir = p.pypkgpath() if pkgdir is None: if p.check(file=1): p = p.dirpath() return p else: return pkgdir.dirpath() class SetupState(object): """ shared state for setting up/tearing down test items or collectors. """ def __init__(self): self.stack = [] def teardown_all(self): while self.stack: col = self.stack.pop() col.teardown() def teardown_exact(self, item): if self.stack and self.stack[-1] == item: col = self.stack.pop() col.teardown() def prepare(self, colitem): """ setup objects along the collector chain to the test-method Teardown any unneccessary previously setup objects.""" needed_collectors = colitem.listchain() while self.stack: if self.stack == needed_collectors[:len(self.stack)]: break col = self.stack.pop() col.teardown() for col in needed_collectors[len(self.stack):]: col.setup() self.stack.append(col) # this is the one per-process instance of py.test configuration config_per_process = Config( pytestplugins=py.test._PytestPlugins(py._com.pyplugins) )