diff options
Diffstat (limited to 'lib/spack/external/_pytest/config.py')
-rw-r--r-- | lib/spack/external/_pytest/config.py | 320 |
1 files changed, 197 insertions, 123 deletions
diff --git a/lib/spack/external/_pytest/config.py b/lib/spack/external/_pytest/config.py index fe386ed0b1..19835d2c39 100644 --- a/lib/spack/external/_pytest/config.py +++ b/lib/spack/external/_pytest/config.py @@ -1,4 +1,5 @@ """ command line options, ini-file and conftest.py processing. """ +from __future__ import absolute_import, division, print_function import argparse import shlex import traceback @@ -7,7 +8,8 @@ import warnings import py # DON't import pytest here because it causes import cycle troubles -import sys, os +import sys +import os import _pytest._code import _pytest.hookspec # the extension point definitions import _pytest.assertion @@ -53,15 +55,15 @@ def main(args=None, plugins=None): return 4 else: try: - config.pluginmanager.check_pending() return config.hook.pytest_cmdline_main(config=config) finally: config._ensure_unconfigure() except UsageError as e: for msg in e.args: - sys.stderr.write("ERROR: %s\n" %(msg,)) + sys.stderr.write("ERROR: %s\n" % (msg,)) return 4 + class cmdline: # compatibility namespace main = staticmethod(main) @@ -70,6 +72,12 @@ class UsageError(Exception): """ error in pytest usage or invocation""" +class PrintHelp(Exception): + """Raised when pytest should print it's help to skip the rest of the + argument parsing and validation.""" + pass + + def filename_arg(path, optname): """ Argparse type validator for filename arguments. @@ -95,10 +103,11 @@ def directory_arg(path, optname): _preinit = [] default_plugins = ( - "mark main terminal runner python fixtures debugging unittest capture skipping " - "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion " - "junitxml resultlog doctest cacheprovider freeze_support " - "setuponly setupplan").split() + "mark main terminal runner python fixtures debugging unittest capture skipping " + "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion " + "junitxml resultlog doctest cacheprovider freeze_support " + "setuponly setupplan warnings").split() + builtin_plugins = set(default_plugins) builtin_plugins.add("pytester") @@ -108,6 +117,7 @@ def _preloadplugins(): assert not _preinit _preinit.append(get_config()) + def get_config(): if _preinit: return _preinit.pop(0) @@ -118,6 +128,7 @@ def get_config(): pluginmanager.import_plugin(spec) return config + def get_plugin_manager(): """ Obtain a new instance of the @@ -129,6 +140,7 @@ def get_plugin_manager(): """ return get_config().pluginmanager + def _prepareconfig(args=None, plugins=None): warning = None if args is None: @@ -153,7 +165,7 @@ def _prepareconfig(args=None, plugins=None): if warning: config.warn('C1', warning) return pluginmanager.hook.pytest_cmdline_parse( - pluginmanager=pluginmanager, args=args) + pluginmanager=pluginmanager, args=args) except BaseException: config._ensure_unconfigure() raise @@ -161,13 +173,14 @@ def _prepareconfig(args=None, plugins=None): class PytestPluginManager(PluginManager): """ - Overwrites :py:class:`pluggy.PluginManager` to add pytest-specific + Overwrites :py:class:`pluggy.PluginManager <_pytest.vendored_packages.pluggy.PluginManager>` to add pytest-specific functionality: * loading plugins from the command line, ``PYTEST_PLUGIN`` env variable and ``pytest_plugins`` global variables found in plugins being loaded; * ``conftest.py`` loading during start-up; """ + def __init__(self): super(PytestPluginManager, self).__init__("pytest", implprefix="pytest_") self._conftest_plugins = set() @@ -198,7 +211,8 @@ class PytestPluginManager(PluginManager): """ .. deprecated:: 2.8 - Use :py:meth:`pluggy.PluginManager.add_hookspecs` instead. + Use :py:meth:`pluggy.PluginManager.add_hookspecs <_pytest.vendored_packages.pluggy.PluginManager.add_hookspecs>` + instead. """ warning = dict(code="I2", fslocation=_pytest._code.getfslineno(sys._getframe(1)), @@ -227,7 +241,7 @@ class PytestPluginManager(PluginManager): def parse_hookspec_opts(self, module_or_class, name): opts = super(PytestPluginManager, self).parse_hookspec_opts( - module_or_class, name) + module_or_class, name) if opts is None: method = getattr(module_or_class, name) if name.startswith("pytest_"): @@ -250,7 +264,10 @@ class PytestPluginManager(PluginManager): ret = super(PytestPluginManager, self).register(plugin, name) if ret: self.hook.pytest_plugin_registered.call_historic( - kwargs=dict(plugin=plugin, manager=self)) + kwargs=dict(plugin=plugin, manager=self)) + + if isinstance(plugin, types.ModuleType): + self.consider_module(plugin) return ret def getplugin(self, name): @@ -265,11 +282,11 @@ class PytestPluginManager(PluginManager): # XXX now that the pluginmanager exposes hookimpl(tryfirst...) # we should remove tryfirst/trylast as markers config.addinivalue_line("markers", - "tryfirst: mark a hook implementation function such that the " - "plugin machinery will try to call it first/as early as possible.") + "tryfirst: mark a hook implementation function such that the " + "plugin machinery will try to call it first/as early as possible.") config.addinivalue_line("markers", - "trylast: mark a hook implementation function such that the " - "plugin machinery will try to call it last/as late as possible.") + "trylast: mark a hook implementation function such that the " + "plugin machinery will try to call it last/as late as possible.") def _warn(self, message): kwargs = message if isinstance(message, dict) else { @@ -293,7 +310,7 @@ class PytestPluginManager(PluginManager): """ current = py.path.local() self._confcutdir = current.join(namespace.confcutdir, abs=True) \ - if namespace.confcutdir else None + if namespace.confcutdir else None self._noconftest = namespace.noconftest testpaths = namespace.file_or_dir foundanchor = False @@ -304,7 +321,7 @@ class PytestPluginManager(PluginManager): if i != -1: path = path[:i] anchor = current.join(path, abs=1) - if exists(anchor): # we found some file object + if exists(anchor): # we found some file object self._try_load_conftest(anchor) foundanchor = True if not foundanchor: @@ -371,7 +388,7 @@ class PytestPluginManager(PluginManager): if path and path.relto(dirpath) or path == dirpath: assert mod not in mods mods.append(mod) - self.trace("loaded conftestmodule %r" %(mod)) + self.trace("loaded conftestmodule %r" % (mod)) self.consider_conftest(mod) return mod @@ -381,7 +398,7 @@ class PytestPluginManager(PluginManager): # def consider_preparse(self, args): - for opt1,opt2 in zip(args, args[1:]): + for opt1, opt2 in zip(args, args[1:]): if opt1 == "-p": self.consider_pluginarg(opt2) @@ -395,38 +412,33 @@ class PytestPluginManager(PluginManager): self.import_plugin(arg) def consider_conftest(self, conftestmodule): - if self.register(conftestmodule, name=conftestmodule.__file__): - self.consider_module(conftestmodule) + self.register(conftestmodule, name=conftestmodule.__file__) def consider_env(self): self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) def consider_module(self, mod): - plugins = getattr(mod, 'pytest_plugins', []) - if isinstance(plugins, str): - plugins = [plugins] - self.rewrite_hook.mark_rewrite(*plugins) - self._import_plugin_specs(plugins) + self._import_plugin_specs(getattr(mod, 'pytest_plugins', [])) def _import_plugin_specs(self, spec): - if spec: - if isinstance(spec, str): - spec = spec.split(",") - for import_spec in spec: - self.import_plugin(import_spec) + plugins = _get_plugin_specs_as_list(spec) + for import_spec in plugins: + self.import_plugin(import_spec) def import_plugin(self, modname): # most often modname refers to builtin modules, e.g. "pytester", # "terminal" or "capture". Those plugins are registered under their # basename for historic purposes but must be imported with the # _pytest prefix. - assert isinstance(modname, str) + assert isinstance(modname, (py.builtin.text, str)), "module name as text required, got %r" % modname + modname = str(modname) if self.get_plugin(modname) is not None: return if modname in builtin_plugins: importspec = "_pytest." + modname else: importspec = modname + self.rewrite_hook.mark_rewrite(importspec) try: __import__(importspec) except ImportError as e: @@ -440,11 +452,28 @@ class PytestPluginManager(PluginManager): import pytest if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception): raise - self._warn("skipped plugin %r: %s" %((modname, e.msg))) + self._warn("skipped plugin %r: %s" % ((modname, e.msg))) else: mod = sys.modules[importspec] self.register(mod, modname) - self.consider_module(mod) + + +def _get_plugin_specs_as_list(specs): + """ + Parses a list of "plugin specs" and returns a list of plugin names. + + Plugin specs can be given as a list of strings separated by "," or already as a list/tuple in + which case it is returned as a list. Specs can also be `None` in which case an + empty list is returned. + """ + if specs is not None: + if isinstance(specs, str): + specs = specs.split(',') if specs else [] + if not isinstance(specs, (list, tuple)): + raise UsageError("Plugin specs must be a ','-separated string or a " + "list/tuple of strings for plugin names. Given: %r" % specs) + return list(specs) + return [] class Parser: @@ -488,7 +517,7 @@ class Parser: for i, grp in enumerate(self._groups): if grp.name == after: break - self._groups.insert(i+1, group) + self._groups.insert(i + 1, group) return group def addoption(self, *opts, **attrs): @@ -526,7 +555,7 @@ class Parser: a = option.attrs() arggroup.add_argument(*n, **a) # bash like autocompletion for dirs (appending '/') - optparser.add_argument(FILE_OR_DIR, nargs='*').completer=filescompleter + optparser.add_argument(FILE_OR_DIR, nargs='*').completer = filescompleter return optparser def parse_setoption(self, args, option, namespace=None): @@ -670,7 +699,7 @@ class Argument: if self._attrs.get('help'): a = self._attrs['help'] a = a.replace('%default', '%(default)s') - #a = a.replace('%prog', '%(prog)s') + # a = a.replace('%prog', '%(prog)s') self._attrs['help'] = a return self._attrs @@ -754,7 +783,7 @@ class MyOptionParser(argparse.ArgumentParser): extra_info = {} self._parser = parser argparse.ArgumentParser.__init__(self, usage=parser._usage, - add_help=False, formatter_class=DropShorterLongHelpFormatter) + add_help=False, formatter_class=DropShorterLongHelpFormatter) # extra_info is a dict of (param -> value) to display if there's # an usage error to provide more contextual information to the user self.extra_info = extra_info @@ -782,9 +811,10 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter): - shortcut if there are only two options and one of them is a short one - cache result on action object as this is called at least 2 times """ + def _format_action_invocation(self, action): orgstr = argparse.HelpFormatter._format_action_invocation(self, action) - if orgstr and orgstr[0] != '-': # only optional arguments + if orgstr and orgstr[0] != '-': # only optional arguments return orgstr res = getattr(action, '_formatted_action_invocation', None) if res: @@ -795,7 +825,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter): action._formatted_action_invocation = orgstr return orgstr return_list = [] - option_map = getattr(action, 'map_long_option', {}) + option_map = getattr(action, 'map_long_option', {}) if option_map is None: option_map = {} short_long = {} @@ -813,7 +843,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter): short_long[shortened] = xxoption # now short_long has been filled out to the longest with dashes # **and** we keep the right option ordering from add_argument - for option in options: # + for option in options: if len(option) == 2 or option[2] == ' ': return_list.append(option) if option[2:] == short_long.get(option.replace('-', '')): @@ -822,22 +852,26 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter): return action._formatted_action_invocation - def _ensure_removed_sysmodule(modname): try: del sys.modules[modname] except KeyError: pass + class CmdOptions(object): """ holds cmdline options as attributes.""" + def __init__(self, values=()): self.__dict__.update(values) + def __repr__(self): - return "<CmdOptions %r>" %(self.__dict__,) + return "<CmdOptions %r>" % (self.__dict__,) + def copy(self): return CmdOptions(self.__dict__) + class Notset: def __repr__(self): return "<NOTSET>" @@ -847,6 +881,18 @@ notset = Notset() FILE_OR_DIR = 'file_or_dir' +def _iter_rewritable_modules(package_files): + for fn in package_files: + is_simple_module = '/' not in fn and fn.endswith('.py') + is_package = fn.count('/') == 1 and fn.endswith('__init__.py') + if is_simple_module: + module_name, _ = os.path.splitext(fn) + yield module_name + elif is_package: + package_name = os.path.dirname(fn) + yield package_name + + class Config(object): """ access to configuration values, pluginmanager and plugin hooks. """ @@ -864,6 +910,7 @@ class Config(object): self.trace = self.pluginmanager.trace.root.get("config") self.hook = self.pluginmanager.hook self._inicache = {} + self._override_ini = () self._opt2dest = {} self._cleanup = [] self._warn = self.pluginmanager._warn @@ -896,11 +943,11 @@ class Config(object): fin = self._cleanup.pop() fin() - def warn(self, code, message, fslocation=None): + def warn(self, code, message, fslocation=None, nodeid=None): """ generate a warning for this test session. """ self.hook.pytest_logwarning.call_historic(kwargs=dict( code=code, message=message, - fslocation=fslocation, nodeid=None)) + fslocation=fslocation, nodeid=nodeid)) def get_terminal_writer(self): return self.pluginmanager.get_plugin("terminalreporter")._tw @@ -916,14 +963,14 @@ class Config(object): else: style = "native" excrepr = excinfo.getrepr(funcargs=True, - showlocals=getattr(option, 'showlocals', False), - style=style, - ) + showlocals=getattr(option, 'showlocals', False), + style=style, + ) res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo) if not py.builtin.any(res): for line in str(excrepr).split("\n"): - sys.stderr.write("INTERNALERROR> %s\n" %line) + sys.stderr.write("INTERNALERROR> %s\n" % line) sys.stderr.flush() def cwd_relative_nodeid(self, nodeid): @@ -964,8 +1011,9 @@ class Config(object): self.invocation_dir = py.path.local() self._parser.addini('addopts', 'extra command line options', 'args') self._parser.addini('minversion', 'minimally required pytest version') + self._override_ini = ns.override_ini or () - def _consider_importhook(self, args, entrypoint_name): + def _consider_importhook(self, args): """Install the PEP 302 import hook if using assertion re-writing. Needs to parse the --assert=<mode> option from the commandline @@ -980,26 +1028,34 @@ class Config(object): except SystemError: mode = 'plain' else: - import pkg_resources - self.pluginmanager.rewrite_hook = hook - for entrypoint in pkg_resources.iter_entry_points('pytest11'): - # 'RECORD' available for plugins installed normally (pip install) - # 'SOURCES.txt' available for plugins installed in dev mode (pip install -e) - # for installed plugins 'SOURCES.txt' returns an empty list, and vice-versa - # so it shouldn't be an issue - for metadata in ('RECORD', 'SOURCES.txt'): - for entry in entrypoint.dist._get_metadata(metadata): - fn = entry.split(',')[0] - is_simple_module = os.sep not in fn and fn.endswith('.py') - is_package = fn.count(os.sep) == 1 and fn.endswith('__init__.py') - if is_simple_module: - module_name, ext = os.path.splitext(fn) - hook.mark_rewrite(module_name) - elif is_package: - package_name = os.path.dirname(fn) - hook.mark_rewrite(package_name) + self._mark_plugins_for_rewrite(hook) self._warn_about_missing_assertion(mode) + def _mark_plugins_for_rewrite(self, hook): + """ + Given an importhook, mark for rewrite any top-level + modules or packages in the distribution package for + all pytest plugins. + """ + import pkg_resources + self.pluginmanager.rewrite_hook = hook + + # 'RECORD' available for plugins installed normally (pip install) + # 'SOURCES.txt' available for plugins installed in dev mode (pip install -e) + # for installed plugins 'SOURCES.txt' returns an empty list, and vice-versa + # so it shouldn't be an issue + metadata_files = 'RECORD', 'SOURCES.txt' + + package_files = ( + entry.split(',')[0] + for entrypoint in pkg_resources.iter_entry_points('pytest11') + for metadata in metadata_files + for entry in entrypoint.dist._get_metadata(metadata) + ) + + for name in _iter_rewritable_modules(package_files): + hook.mark_rewrite(name) + def _warn_about_missing_assertion(self, mode): try: assert False @@ -1023,19 +1079,17 @@ class Config(object): args[:] = shlex.split(os.environ.get('PYTEST_ADDOPTS', '')) + args args[:] = self.getini("addopts") + args self._checkversion() - entrypoint_name = 'pytest11' - self._consider_importhook(args, entrypoint_name) + self._consider_importhook(args) self.pluginmanager.consider_preparse(args) - self.pluginmanager.load_setuptools_entrypoints(entrypoint_name) + self.pluginmanager.load_setuptools_entrypoints('pytest11') self.pluginmanager.consider_env() self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy()) - confcutdir = self.known_args_namespace.confcutdir if self.known_args_namespace.confcutdir is None and self.inifile: confcutdir = py.path.local(self.inifile).dirname self.known_args_namespace.confcutdir = confcutdir try: self.hook.pytest_load_initial_conftests(early_config=self, - args=args, parser=self._parser) + args=args, parser=self._parser) except ConftestImportFailure: e = sys.exc_info()[1] if ns.help or ns.version: @@ -1053,28 +1107,32 @@ class Config(object): 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__)) + "%s:%d: requires pytest-%s, actual pytest-%s'" % ( + self.inicfg.config.path, self.inicfg.lineof('minversion'), + minver, pytest.__version__)) def parse(self, args, addopts=True): # parse given cmdline arguments into this config object. assert not hasattr(self, 'args'), ( - "can only parse cmdline args at most once per Config object") + "can only parse cmdline args at most once per Config object") self._origargs = args self.hook.pytest_addhooks.call_historic( - kwargs=dict(pluginmanager=self.pluginmanager)) + kwargs=dict(pluginmanager=self.pluginmanager)) self._preparse(args, addopts=addopts) # XXX deprecated hook: self.hook.pytest_cmdline_preparse(config=self, args=args) - args = self._parser.parse_setoption(args, self.option, namespace=self.option) - if not args: - cwd = os.getcwd() - if cwd == self.rootdir: - args = self.getini('testpaths') + self._parser.after_preparse = True + try: + args = self._parser.parse_setoption(args, self.option, namespace=self.option) if not args: - args = [cwd] - self.args = args + cwd = os.getcwd() + if cwd == self.rootdir: + args = self.getini('testpaths') + if not args: + args = [cwd] + self.args = args + except PrintHelp: + pass def addinivalue_line(self, name, line): """ add a line to an ini-file option. The option must have been @@ -1082,12 +1140,12 @@ class Config(object): the first line in its value. """ x = self.getini(name) assert isinstance(x, list) - x.append(line) # modifies the cached list inline + x.append(line) # modifies the cached list inline def getini(self, name): """ return configuration value from an :ref:`ini file <inifiles>`. If the specified name hasn't been registered through a prior - :py:func:`parser.addini <pytest.config.Parser.addini>` + :py:func:`parser.addini <_pytest.config.Parser.addini>` call (usually from a plugin), a ValueError is raised. """ try: return self._inicache[name] @@ -1099,7 +1157,7 @@ class Config(object): try: description, type, default = self._parser._inidict[name] except KeyError: - raise ValueError("unknown configuration value: %r" %(name,)) + raise ValueError("unknown configuration value: %r" % (name,)) value = self._get_override_ini_value(name) if value is None: try: @@ -1112,10 +1170,10 @@ class Config(object): return [] if type == "pathlist": dp = py.path.local(self.inicfg.config.path).dirpath() - l = [] + values = [] for relpath in shlex.split(value): - l.append(dp.join(relpath, abs=True)) - return l + values.append(dp.join(relpath, abs=True)) + return values elif type == "args": return shlex.split(value) elif type == "linelist": @@ -1132,13 +1190,13 @@ class Config(object): except KeyError: return None modpath = py.path.local(mod.__file__).dirpath() - l = [] + values = [] 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 + values.append(relroot) + return values def _get_override_ini_value(self, name): value = None @@ -1146,15 +1204,14 @@ class Config(object): # and -o foo1=bar1 -o foo2=bar2 options # always use the last item if multiple value set for same ini-name, # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2 - if self.getoption("override_ini", None): - for ini_config_list in self.option.override_ini: - for ini_config in ini_config_list: - try: - (key, user_ini_value) = ini_config.split("=", 1) - except ValueError: - raise UsageError("-o/--override-ini expects option=value style.") - if key == name: - value = user_ini_value + for ini_config_list in self._override_ini: + for ini_config in ini_config_list: + try: + (key, user_ini_value) = ini_config.split("=", 1) + except ValueError: + raise UsageError("-o/--override-ini expects option=value style.") + if key == name: + value = user_ini_value return value def getoption(self, name, default=notset, skip=False): @@ -1177,7 +1234,7 @@ class Config(object): return default if skip: import pytest - pytest.skip("no %r option found" %(name,)) + pytest.skip("no %r option found" % (name,)) raise ValueError("no option named %r" % (name,)) def getvalue(self, name, path=None): @@ -1188,12 +1245,14 @@ class Config(object): """ (deprecated, use getoption(skip=True)) """ return self.getoption(name, skip=True) + def exists(path, ignore=EnvironmentError): try: return path.check() except ignore: return False + def getcfg(args, warnfunc=None): """ Search the list of arguments for a valid ini-file for pytest, @@ -1228,25 +1287,20 @@ def getcfg(args, warnfunc=None): return None, None, None -def get_common_ancestor(args): - # args are what we get after early command line parsing (usually - # strings, but can be py.path.local objects as well) +def get_common_ancestor(paths): common_ancestor = None - for arg in args: - if str(arg)[0] == "-": - continue - p = py.path.local(arg) - if not p.exists(): + for path in paths: + if not path.exists(): continue if common_ancestor is None: - common_ancestor = p + common_ancestor = path else: - if p.relto(common_ancestor) or p == common_ancestor: + if path.relto(common_ancestor) or path == common_ancestor: continue - elif common_ancestor.relto(p): - common_ancestor = p + elif common_ancestor.relto(path): + common_ancestor = path else: - shared = p.common(common_ancestor) + shared = path.common(common_ancestor) if shared is not None: common_ancestor = shared if common_ancestor is None: @@ -1257,9 +1311,29 @@ def get_common_ancestor(args): def get_dirs_from_args(args): - return [d for d in (py.path.local(x) for x in args - if not str(x).startswith("-")) - if d.exists()] + def is_option(x): + return str(x).startswith('-') + + def get_file_part_from_node_id(x): + return str(x).split('::')[0] + + def get_dir_from_path(path): + if path.isdir(): + return path + return py.path.local(path.dirname) + + # These look like paths but may not exist + possible_paths = ( + py.path.local(get_file_part_from_node_id(arg)) + for arg in args + if not is_option(arg) + ) + + return [ + get_dir_from_path(path) + for path in possible_paths + if path.exists() + ] def determine_setup(inifile, args, warnfunc=None): @@ -1282,7 +1356,7 @@ def determine_setup(inifile, args, warnfunc=None): rootdir, inifile, inicfg = getcfg(dirs, warnfunc=warnfunc) if rootdir is None: rootdir = get_common_ancestor([py.path.local(), ancestor]) - is_fs_root = os.path.splitdrive(str(rootdir))[1] == os.sep + is_fs_root = os.path.splitdrive(str(rootdir))[1] == '/' if is_fs_root: rootdir = ancestor return rootdir, inifile, inicfg or {} @@ -1304,7 +1378,7 @@ def setns(obj, dic): else: setattr(obj, name, value) obj.__all__.append(name) - #if obj != pytest: + # if obj != pytest: # pytest.__all__.append(name) setattr(pytest, name, value) |