import os import py from .exceptions import UsageError def exists(path, ignore=EnvironmentError): try: return path.check() except ignore: return False def getcfg(args, config=None): """ Search the list of arguments for a valid ini-file for pytest, and return a tuple of (rootdir, inifile, cfg-dict). note: config is optional and used only to issue warnings explicitly (#2891). """ from _pytest.deprecated import CFG_PYTEST_SECTION inibasenames = ["pytest.ini", "tox.ini", "setup.cfg"] args = [x for x in args if not str(x).startswith("-")] if not args: args = [py.path.local()] for arg in args: arg = py.path.local(arg) for base in arg.parts(reverse=True): for inibasename in inibasenames: p = base.join(inibasename) if exists(p): iniconfig = py.iniconfig.IniConfig(p) if "pytest" in iniconfig.sections: if inibasename == "setup.cfg" and config is not None: from _pytest.warnings import _issue_config_warning from _pytest.warning_types import RemovedInPytest4Warning _issue_config_warning( RemovedInPytest4Warning( CFG_PYTEST_SECTION.format(filename=inibasename) ), config=config, ) return base, p, iniconfig["pytest"] if ( inibasename == "setup.cfg" and "tool:pytest" in iniconfig.sections ): return base, p, iniconfig["tool:pytest"] elif inibasename == "pytest.ini": # allowed to be empty return base, p, {} return None, None, None def get_common_ancestor(paths): common_ancestor = None for path in paths: if not path.exists(): continue if common_ancestor is None: common_ancestor = path else: if path.relto(common_ancestor) or path == common_ancestor: continue elif common_ancestor.relto(path): common_ancestor = path else: shared = path.common(common_ancestor) if shared is not None: common_ancestor = shared if common_ancestor is None: common_ancestor = py.path.local() elif common_ancestor.isfile(): common_ancestor = common_ancestor.dirpath() return common_ancestor def get_dirs_from_args(args): 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, rootdir_cmd_arg=None, config=None): dirs = get_dirs_from_args(args) if inifile: iniconfig = py.iniconfig.IniConfig(inifile) is_cfg_file = str(inifile).endswith(".cfg") sections = ["tool:pytest", "pytest"] if is_cfg_file else ["pytest"] for section in sections: try: inicfg = iniconfig[section] if is_cfg_file and section == "pytest" and config is not None: from _pytest.deprecated import CFG_PYTEST_SECTION from _pytest.warnings import _issue_config_warning # TODO: [pytest] section in *.cfg files is deprecated. Need refactoring once # the deprecation expires. _issue_config_warning( CFG_PYTEST_SECTION.format(filename=str(inifile)), config ) break except KeyError: inicfg = None rootdir = get_common_ancestor(dirs) else: ancestor = get_common_ancestor(dirs) rootdir, inifile, inicfg = getcfg([ancestor], config=config) if rootdir is None: for rootdir in ancestor.parts(reverse=True): if rootdir.join("setup.py").exists(): break else: rootdir, inifile, inicfg = getcfg(dirs, config=config) if rootdir is None: rootdir = get_common_ancestor([py.path.local(), ancestor]) is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/" if is_fs_root: rootdir = ancestor if rootdir_cmd_arg: rootdir_abs_path = py.path.local(os.path.expandvars(rootdir_cmd_arg)) if not os.path.isdir(str(rootdir_abs_path)): raise UsageError( "Directory '{}' not found. Check your '--rootdir' option.".format( rootdir_abs_path ) ) rootdir = rootdir_abs_path return rootdir, inifile, inicfg or {}