def exit(reason: str = "", returncode: Optional[int] = None, *, msg: Optional[str] = None) -> "NoReturn": """Exit testing process. :param reason: The message to show as the reason for exiting pytest. reason has a default value only because `msg` is deprecated. :param returncode: Return code to be used when exiting pytest. :param msg: Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. """ __tracebackhide__ = True from _pytest.config import UsageError if reason and msg: raise UsageError( "cannot pass reason and msg to exit(), `msg` is deprecated, use `reason`." ) if not reason: if msg is None: raise UsageError("exit() requires a reason argument") warnings.warn(KEYWORD_MSG_ARG.format(func="exit"), stacklevel=2) reason = msg raise Exit(reason, returncode)
def build_sosu_config(args: argparse.Namespace, env: os._Environ) -> SosuConfig: username = args.sosu_username or env.get("SAUCE_USERNAME") access_key = args.sosu_access_key or env.get("SAUCE_ACCESS_KEY") if not username: raise UsageError("--sosu-username or SAUCE_USERNAME are not provided") if not access_key: raise UsageError( "--sosu-access-key or SAUCE_ACCESS_KEY are not provided") region = args.sosu_region or env.get("SAUCE_REGION") webdriver_url = args.sosu_webdriver_url or env.get("SAUCE_WEBDRIVER_URL") if not webdriver_url: region = "us-west-1" host = get_host_by_region(region) webdriver_url_data = WebDriverUrlData(host=host) else: try: webdriver_url_data = WebDriverUrlData.from_url(webdriver_url) except ValueError: raise UsageError("Invalid WebDriver URL") from None return SosuConfig( username=username, access_key=access_key, region=region, webdriver_url_data=webdriver_url_data, )
def matchkeyword(colitem, keywordexpr): """Tries to match given keyword expression to given collector item. Will match on the name of colitem, including the names of its parents. Only matches names of items which are either a :class:`Class` or a :class:`Function`. Additionally, matches on names in the 'extra_keyword_matches' set of any item, as well as names directly assigned to test functions. """ mapping = KeywordMapping.from_item(colitem) if " " not in keywordexpr: # special case to allow for simple "-k pass" and "-k 1.3" return mapping[keywordexpr] elif keywordexpr.startswith("not ") and " " not in keywordexpr[4:]: return not mapping[keywordexpr[4:]] for kwd in keywordexpr.split(): if keyword.iskeyword(kwd) and kwd not in python_keywords_allowed_list: raise UsageError( "Python keyword '{}' not accepted in expressions passed to '-k'" .format(kwd)) try: return eval(keywordexpr, {}, mapping) except Exception: raise UsageError( "Wrong expression passed to '-k': {}".format(keywordexpr))
def pytest_collection_modifyitems(config, items): redis_uri = config.getoption("--redis") if redis_uri is None: skip_redis = pytest.mark.skip( reason="need --redis option with redis URI to run" ) for item in items: if "redis" in item.keywords: item.add_marker(skip_redis) return redis_version = int(aioredis.__version__.split(".")[0]) options = None if redis_version == 1: (host, port), options = aioredis.util.parse_url(redis_uri) options.update({'host': host, 'port': port}) elif redis_version == 2: try: options = aioredis.connection.parse_url(redis_uri) except ValueError as e: raise UsageError(f"Invalid redis URI {redis_uri!r}: {e}") try: assert isinstance(options, dict), \ "Only redis and rediss schemas are supported, eg redis://foo." except AssertionError as e: raise UsageError(f"Invalid redis URI {redis_uri!r}: {e}")
def _parsearg(self, arg: str) -> Tuple[py.path.local, List[str]]: """ return (fspath, names) tuple after checking the file exists. """ strpath, *parts = str(arg).split("::") if self.config.option.pyargs: strpath = self._tryconvertpyarg(strpath) relpath = strpath.replace("/", os.sep) fspath = self.config.invocation_dir.join(relpath, abs=True) if not fspath.check(): if self.config.option.pyargs: raise UsageError("file or package not found: " + arg + " (missing __init__.py?)") raise UsageError("file not found: " + arg) return (fspath, parts)
def _parsearg(self, arg): """ return (fspath, names) tuple after checking the file exists. """ parts = str(arg).split("::") if self.config.option.pyargs: parts[0] = self._tryconvertpyarg(parts[0]) relpath = parts[0].replace("/", os.sep) path = self.config.invocation_dir.join(relpath, abs=True) if not path.check(): if self.config.option.pyargs: raise UsageError("file or package not found: " + arg + " (missing __init__.py?)") raise UsageError("file not found: " + arg) parts[0] = path.realpath() return parts
def resolve_collection_argument( invocation_path: Path, arg: str, *, as_pypath: bool = False ) -> Tuple[Path, List[str]]: """Parse path arguments optionally containing selection parts and return (fspath, names). Command-line arguments can point to files and/or directories, and optionally contain parts for specific tests selection, for example: "pkg/tests/test_foo.py::TestClass::test_foo" This function ensures the path exists, and returns a tuple: (Path("/full/path/to/pkg/tests/test_foo.py"), ["TestClass", "test_foo"]) When as_pypath is True, expects that the command-line argument actually contains module paths instead of file-system paths: "pkg.tests.test_foo::TestClass::test_foo" In which case we search sys.path for a matching module, and then return the *path* to the found module. If the path doesn't exist, raise UsageError. If the path is a directory and selection parts are present, raise UsageError. """ base, squacket, rest = str(arg).partition("[") strpath, *parts = base.split("::") if parts: parts[-1] = f"{parts[-1]}{squacket}{rest}" if as_pypath: strpath = search_pypath(strpath) fspath = invocation_path / strpath fspath = absolutepath(fspath) if not fspath.exists(): msg = ( "module or package not found: {arg} (missing __init__.py?)" if as_pypath else "file or directory not found: {arg}" ) raise UsageError(msg.format(arg=arg)) if parts and fspath.is_dir(): msg = ( "package argument cannot contain :: selection parts: {arg}" if as_pypath else "directory argument cannot contain :: selection parts: {arg}" ) raise UsageError(msg.format(arg=arg)) return fspath, parts
def matchmark(colitem, markexpr): """Tries to match on any marker names, attached to the given colitem.""" try: return eval(markexpr, {}, MarkMapping.from_item(colitem)) except Exception: raise UsageError( "Wrong expression passed to '-m': {}".format(markexpr))
def _perform_collect(self, args, genitems): if args is None: args = self.config.args self.trace("perform_collect", self, args) self.trace.root.indent += 1 self._notfound = [] self._initialpaths = set() self._initialparts = [] self.items = items = [] for arg in args: parts = self._parsearg(arg) self._initialparts.append(parts) self._initialpaths.add(parts[0]) rep = collect_one_node(self) self.ihook.pytest_collectreport(report=rep) self.trace.root.indent -= 1 if self._notfound: errors = [] for arg, exc in self._notfound: line = "(no name %r in any of %r)" % (arg, exc.args[0]) errors.append("not found: %s\n%s" % (arg, line)) # XXX: test this raise UsageError(*errors) if not genitems: return rep.result else: if rep.passed: for node in rep.result: self.items.extend(self.genitems(node)) return items
def _resolve_msg_to_reason(func_name: str, reason: str, msg: Optional[str] = None) -> str: """ Handles converting the deprecated msg parameter if provided into reason, raising a deprecation warning. This function will be removed when the optional msg argument is removed from here in future. :param str func_name: The name of the offending function, this is formatted into the deprecation message. :param str reason: The reason= passed into either pytest.fail() or pytest.skip() :param str msg: The msg= passed into either pytest.fail() or pytest.skip(). This will be converted into reason if it is provided to allow pytest.skip(msg=) or pytest.fail(msg=) to continue working in the interim period. :returns: The value to use as reason. """ __tracebackhide__ = True if msg is not None: if reason: from pytest import UsageError raise UsageError( f"Passing both ``reason`` and ``msg`` to pytest.{func_name}(...) is not permitted." ) warnings.warn(KEYWORD_MSG_ARG.format(func=func_name), stacklevel=3) reason = msg return reason
def matchmark(colitem, markexpr: str) -> bool: """Tries to match on any marker names, attached to the given colitem.""" try: return evaluate(markexpr, MarkMatcher.from_item(colitem)) except ParseError as e: raise UsageError("Wrong expression passed to '-m': {}: {}".format( markexpr, e)) from None
def _perform_collect( # noqa: F811 self, args: Optional[Sequence[str]], genitems: bool) -> Union[ List[Union[nodes.Item]], List[Union[nodes.Item, nodes.Collector]]]: if args is None: args = self.config.args self.trace("perform_collect", self, args) self.trace.root.indent += 1 self._notfound = [] # type: List[Tuple[str, NoMatch]] initialpaths = [] # type: List[py.path.local] self._initial_parts = [] # type: List[Tuple[py.path.local, List[str]]] self.items = items = [] # type: List[nodes.Item] for arg in args: fspath, parts = self._parsearg(arg) self._initial_parts.append((fspath, parts)) initialpaths.append(fspath) self._initialpaths = frozenset(initialpaths) rep = collect_one_node(self) self.ihook.pytest_collectreport(report=rep) self.trace.root.indent -= 1 if self._notfound: errors = [] for arg, exc in self._notfound: line = "(no name {!r} in any of {!r})".format(arg, exc.args[0]) errors.append("not found: {}\n{}".format(arg, line)) raise UsageError(*errors) if not genitems: return rep.result else: if rep.passed: for node in rep.result: self.items.extend(self.genitems(node)) return items
def redis_options(request): redis_uri = request.config.getoption("--redis") if redis_uri is None: pytest.skip("need --redis option with redis URI to run") return redis_version = int(aioredis.__version__.split(".")[0]) if redis_version == 1: (host, port), options = aioredis.util.parse_url(redis_uri) options.update({'host': host, 'port': port}) return options if redis_version == 2: try: return aioredis.connection.parse_url(redis_uri) except ValueError as e: raise UsageError(f"Invalid redis URI {redis_uri!r}: {e}") raise UsageError("Unsupported aioredis version")
def matchkeyword(colitem, keywordexpr): """Tries to match given keyword expression to given collector item. Will match on the name of colitem, including the names of its parents. Only matches names of items which are either a :class:`Class` or a :class:`Function`. Additionally, matches on names in the 'extra_keyword_matches' set of any item, as well as names directly assigned to test functions. """ mapped_names = set() # Add the names of the current item and any parent items import pytest for item in colitem.listchain(): if not isinstance(item, pytest.Instance): mapped_names.add(item.name) # Add the names added as extra keywords to current or parent items for name in colitem.listextrakeywords(): mapped_names.add(name) # Add the names attached to the current function through direct assignment if hasattr(colitem, 'function'): for name in colitem.function.__dict__: mapped_names.add(name) mapping = KeywordMapping(mapped_names) if " " not in keywordexpr: # special case to allow for simple "-k pass" and "-k 1.3" return mapping[keywordexpr] elif keywordexpr.startswith("not ") and " " not in keywordexpr[4:]: return not mapping[keywordexpr[4:]] for kwd in keywordexpr.split(): if keyword.iskeyword(kwd) and kwd not in python_keywords_allowed_list: raise UsageError( "Python keyword '{}' not accepted in expressions passed to '-k'" .format(kwd)) try: return eval(keywordexpr, {}, mapping) except SyntaxError: raise UsageError( "Wrong expression passed to '-k': {}".format(keywordexpr))
def pytest_configure(config: Config) -> None: config._store[old_mark_config_key] = MARK_GEN._config MARK_GEN._config = config empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION) if empty_parameterset not in ("skip", "xfail", "fail_at_collect", None, ""): raise UsageError("{!s} must be one of skip, xfail or fail_at_collect" " but it is {!r}".format(EMPTY_PARAMETERSET_OPTION, empty_parameterset))
def pytest_configure(config): config._old_mark_config = MARK_GEN._config if config.option.strict: MARK_GEN._config = config empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION) if empty_parameterset not in ('skip', 'xfail', None, ''): raise UsageError("{!s} must be one of skip and xfail," " but it is {!r}".format(EMPTY_PARAMETERSET_OPTION, empty_parameterset))
def pytest_collection_modifyitems(config): diva_path = config.getoption('--divadir') if diva_path: os.environ['PATH'] += os.pathsep + os.path.abspath(diva_path) try: subprocess.check_call(['diva', '--version']) except (subprocess.CalledProcessError, OSError): msg = "Can't find diva on PATH" if diva_path is None: msg += " and --divadir not provided" else: msg += " or in {} (provided with --divadir)".format(diva_path) raise UsageError(msg)
def matchkeyword(colitem, keywordexpr: str) -> bool: """Tries to match given keyword expression to given collector item. Will match on the name of colitem, including the names of its parents. Only matches names of items which are either a :class:`Class` or a :class:`Function`. Additionally, matches on names in the 'extra_keyword_matches' set of any item, as well as names directly assigned to test functions. """ try: return evaluate(keywordexpr, KeywordMatcher.from_item(colitem)) except ParseError as e: raise UsageError("Wrong expression passed to '-k': {}: {}".format( keywordexpr, e)) from None
def deselect_by_mark(items: "List[Item]", config: Config) -> None: matchexpr = config.option.markexpr if not matchexpr: return try: expression = Expression.compile(matchexpr) except ParseError as e: raise UsageError(f"Wrong expression passed to '-m': {matchexpr}: {e}") from None remaining = [] deselected = [] for item in items: if expression.evaluate(MarkMatcher.from_item(item)): remaining.append(item) else: deselected.append(item) if deselected: config.hook.pytest_deselected(items=deselected) items[:] = remaining
def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[int]: for setting_name in setting_names: log_level = config.getoption(setting_name) if log_level is None: log_level = config.getini(setting_name) if log_level: break else: return None if isinstance(log_level, str): log_level = log_level.upper() try: return int(getattr(logging, log_level, log_level)) except ValueError as e: # Python logging does not recognise this as a logging level raise UsageError("'{}' is not recognized as a logging level name for " "'{}'. Please consider passing the " "logging level num instead.".format( log_level, setting_name)) from e
def deselect_by_keyword(items: "List[Item]", config: Config) -> None: keywordexpr = config.option.keyword.lstrip() if not keywordexpr: return if keywordexpr.startswith("-"): # To be removed in pytest 7.0.0. # Uncomment this after 6.0 release (#7361) # warnings.warn(MINUS_K_DASH, stacklevel=2) keywordexpr = "not " + keywordexpr[1:] selectuntil = False if keywordexpr[-1:] == ":": # To be removed in pytest 7.0.0. # Uncomment this after 6.0 release (#7361) # warnings.warn(MINUS_K_COLON, stacklevel=2) selectuntil = True keywordexpr = keywordexpr[:-1] try: expression = Expression.compile(keywordexpr) except ParseError as e: raise UsageError("Wrong expression passed to '-k': {}: {}".format( keywordexpr, e)) from None remaining = [] deselected = [] for colitem in items: if keywordexpr and not expression.evaluate( KeywordMatcher.from_item(colitem)): deselected.append(colitem) else: if selectuntil: keywordexpr = None remaining.append(colitem) if deselected: config.hook.pytest_deselected(items=deselected) items[:] = remaining
def resolve_collection_argument( invocation_dir: py.path.local, arg: str, *, as_pypath: bool = False ) -> Tuple[py.path.local, List[str]]: """Parse path arguments optionally containing selection parts and return (fspath, names). Command-line arguments can point to files and/or directories, and optionally contain parts for specific tests selection, for example: "pkg/tests/test_foo.py::TestClass::test_foo" This function ensures the path exists, and returns a tuple: (py.path.path("/full/path/to/pkg/tests/test_foo.py"), ["TestClass", "test_foo"]) When as_pypath is True, expects that the command-line argument actually contains module paths instead of file-system paths: "pkg.tests.test_foo::TestClass::test_foo" In which case we search sys.path for a matching module, and then return the *path* to the found module. If the path doesn't exist, raise UsageError. """ strpath, *parts = str(arg).split("::") if as_pypath: strpath = search_pypath(strpath) fspath = Path(str(invocation_dir), strpath) fspath = absolutepath(fspath) if not fspath.exists(): msg = ( "module or package not found: {arg} (missing __init__.py?)" if as_pypath else "file or directory not found: {arg}" ) raise UsageError(msg.format(arg=arg)) return py.path.local(str(fspath)), parts
def perform_collect( self, args: Optional[Sequence[str]] = None, genitems: bool = True ) -> Sequence[Union[nodes.Item, nodes.Collector]]: """Perform the collection phase for this session. This is called by the default :func:`pytest_collection <_pytest.hookspec.pytest_collection>` hook implementation; see the documentation of this hook for more details. For testing purposes, it may also be called directly on a fresh ``Session``. This function normally recursively expands any collectors collected from the session to their items, and only items are returned. For testing purposes, this may be suppressed by passing ``genitems=False``, in which case the return value contains these collectors unexpanded, and ``session.items`` is empty. """ if args is None: args = self.config.args self.trace("perform_collect", self, args) self.trace.root.indent += 1 self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = [] self._initial_parts: List[Tuple[Path, List[str]]] = [] self.items: List[nodes.Item] = [] hook = self.config.hook items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items try: initialpaths: List[Path] = [] for arg in args: fspath, parts = resolve_collection_argument( self.config.invocation_params.dir, arg, as_pypath=self.config.option.pyargs, ) self._initial_parts.append((fspath, parts)) initialpaths.append(fspath) self._initialpaths = frozenset(initialpaths) rep = collect_one_node(self) self.ihook.pytest_collectreport(report=rep) self.trace.root.indent -= 1 if self._notfound: errors = [] for arg, cols in self._notfound: line = f"(no name {arg!r} in any of {cols!r})" errors.append(f"not found: {arg}\n{line}") raise UsageError(*errors) if not genitems: items = rep.result else: if rep.passed: for node in rep.result: self.items.extend(self.genitems(node)) self.config.pluginmanager.check_pending() hook.pytest_collection_modifyitems( session=self, config=self.config, items=items ) finally: hook.pytest_collection_finish(session=self) self.testscollected = len(items) return items
def _parse_expression(expr: str, exc_message: str) -> Expression: try: return Expression.compile(expr) except ParseError as e: raise UsageError(f"{exc_message}: {expr}: {e}") from None