diff --git a/AUTHORS b/AUTHORS index 9f6ee048d9a..a64c95acb6b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -176,7 +176,6 @@ mbyt Michael Aquilina Michael Birtwell Michael Droettboom -Michael Goerz Michael Seifert Michal Wajszczuk Mihai Capotă diff --git a/changelog/1682.deprecation.rst b/changelog/1682.deprecation.rst deleted file mode 100644 index 741164eb67b..00000000000 --- a/changelog/1682.deprecation.rst +++ /dev/null @@ -1,2 +0,0 @@ -Passing arguments to pytest.fixture() as positional arguments is deprecated - pass them -as a keyword argument instead. diff --git a/changelog/1682.feature.rst b/changelog/1682.feature.rst deleted file mode 100644 index 392de636390..00000000000 --- a/changelog/1682.feature.rst +++ /dev/null @@ -1,3 +0,0 @@ -The ``scope`` parameter of ``@pytest.fixture`` can now be a callable that receives -the fixture name and the ``config`` object as keyword-only parameters. -See `the docs `__ for more information. diff --git a/changelog/5056.trivial.rst b/changelog/5056.trivial.rst deleted file mode 100644 index 75e01a88b1d..00000000000 --- a/changelog/5056.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -The HelpFormatter uses ``py.io.get_terminal_width`` for better width detection. diff --git a/changelog/5764.feature.rst b/changelog/5764.feature.rst deleted file mode 100644 index 3ac77b8fe7d..00000000000 --- a/changelog/5764.feature.rst +++ /dev/null @@ -1 +0,0 @@ -New behavior of the ``--pastebin`` option: failures to connect to the pastebin server are reported, without failing the pytest run diff --git a/changelog/5806.bugfix.rst b/changelog/5806.bugfix.rst deleted file mode 100644 index ec887768ddd..00000000000 --- a/changelog/5806.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix "lexer" being used when uploading to bpaste.net from ``--pastebin`` to "text". diff --git a/doc/en/example/costlysetup/conftest.py b/doc/en/example/costlysetup/conftest.py index 80355983466..8fa9c9fe475 100644 --- a/doc/en/example/costlysetup/conftest.py +++ b/doc/en/example/costlysetup/conftest.py @@ -1,7 +1,7 @@ import pytest -@pytest.fixture(scope="session") +@pytest.fixture("session") def setup(request): setup = CostlySetup() yield setup diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index d5f4a85e3a9..233d4045c8a 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -301,32 +301,6 @@ are finalized when the last test of a *package* finishes. Use this new feature sparingly and please make sure to report any issues you find. -Dynamic scope -^^^^^^^^^^^^^ - -In some cases, you might want to change the scope of the fixture without changing the code. -To do that, pass a callable to ``scope``. The callable must return a string with a valid scope -and will be executed only once - during the fixture definition. It will be called with two -keyword arguments - ``fixture_name`` as a string and ``config`` with a configuration object. - -This can be especially useful when dealing with fixtures that need time for setup, like spawning -a docker container. You can use the command-line argument to control the scope of the spawned -containers for different environments. See the example below. - -.. code-block:: python - - def determine_scope(fixture_name, config): - if config.getoption("--keep-containers"): - return "session" - return "function" - - - @pytest.fixture(scope=determine_scope) - def docker_container(): - yield spawn_container() - - - Order: Higher-scoped fixtures are instantiated first ---------------------------------------------------- diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 534bfe2a831..a0f4d15ce8a 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -5,15 +5,10 @@ from inspect import CO_VARARGS from inspect import CO_VARKEYWORDS from traceback import format_exception_only -from types import CodeType from types import TracebackType -from typing import Any -from typing import Dict from typing import Generic -from typing import List from typing import Optional from typing import Pattern -from typing import Set from typing import Tuple from typing import TypeVar from typing import Union @@ -34,7 +29,7 @@ class Code: """ wrapper around Python code objects """ - def __init__(self, rawcode) -> None: + def __init__(self, rawcode): if not hasattr(rawcode, "co_filename"): rawcode = getrawcode(rawcode) try: @@ -43,7 +38,7 @@ def __init__(self, rawcode) -> None: self.name = rawcode.co_name except AttributeError: raise TypeError("not a code object: {!r}".format(rawcode)) - self.raw = rawcode # type: CodeType + self.raw = rawcode def __eq__(self, other): return self.raw == other.raw @@ -356,7 +351,7 @@ def recursionindex(self): """ return the index of the frame/TracebackEntry where recursion originates if appropriate, None if no recursion occurred """ - cache = {} # type: Dict[Tuple[Any, int, int], List[Dict[str, Any]]] + cache = {} for i, entry in enumerate(self): # id for the code.raw is needed to work around # the strange metaprogramming in the decorator lib from pypi @@ -655,7 +650,7 @@ def repr_args(self, entry): args.append((argname, saferepr(argvalue))) return ReprFuncArgs(args) - def get_source(self, source, line_index=-1, excinfo=None, short=False) -> List[str]: + def get_source(self, source, line_index=-1, excinfo=None, short=False): """ return formatted and marked up source lines. """ import _pytest._code @@ -727,7 +722,7 @@ def repr_traceback_entry(self, entry, excinfo=None): else: line_index = entry.lineno - entry.getfirstlinesource() - lines = [] # type: List[str] + lines = [] style = entry._repr_style if style is None: style = self.style @@ -804,7 +799,7 @@ def _truncate_recursive_traceback(self, traceback): exc_msg=str(e), max_frames=max_frames, total=len(traceback), - ) # type: Optional[str] + ) traceback = traceback[:max_frames] + traceback[-max_frames:] else: if recursionindex is not None: @@ -817,12 +812,10 @@ def _truncate_recursive_traceback(self, traceback): def repr_excinfo(self, excinfo): - repr_chain = ( - [] - ) # type: List[Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]]] + repr_chain = [] e = excinfo.value descr = None - seen = set() # type: Set[int] + seen = set() while e is not None and id(e) not in seen: seen.add(id(e)) if excinfo: @@ -875,8 +868,8 @@ def __repr__(self): class ExceptionRepr(TerminalRepr): - def __init__(self) -> None: - self.sections = [] # type: List[Tuple[str, str, str]] + def __init__(self): + self.sections = [] def addsection(self, name, content, sep="-"): self.sections.append((name, content, sep)) diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py index db78bbd0d35..ea2fc5e3f53 100644 --- a/src/_pytest/_code/source.py +++ b/src/_pytest/_code/source.py @@ -7,7 +7,6 @@ import warnings from ast import PyCF_ONLY_AST as _AST_FLAG from bisect import bisect_right -from typing import List import py @@ -20,11 +19,11 @@ class Source: _compilecounter = 0 def __init__(self, *parts, **kwargs): - self.lines = lines = [] # type: List[str] + self.lines = lines = [] de = kwargs.get("deindent", True) for part in parts: if not part: - partlines = [] # type: List[str] + partlines = [] elif isinstance(part, Source): partlines = part.lines elif isinstance(part, (tuple, list)): @@ -158,7 +157,8 @@ def compile( source = "\n".join(self.lines) + "\n" try: co = compile(source, filename, mode, flag) - except SyntaxError as ex: + except SyntaxError: + ex = sys.exc_info()[1] # re-represent syntax errors from parsing python strings msglines = self.lines[: ex.lineno] if ex.offset: @@ -173,8 +173,7 @@ def compile( if flag & _AST_FLAG: return co lines = [(x + "\n") for x in self.lines] - # Type ignored because linecache.cache is private. - linecache.cache[filename] = (1, None, lines, filename) # type: ignore + linecache.cache[filename] = (1, None, lines, filename) return co @@ -283,7 +282,7 @@ def get_statement_startend2(lineno, node): return start, end -def getstatementrange_ast(lineno, source: Source, assertion=False, astnode=None): +def getstatementrange_ast(lineno, source, assertion=False, astnode=None): if astnode is None: content = str(source) # See #4260: diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py index 3b42b356d5b..126929b6ad9 100644 --- a/src/_pytest/assertion/__init__.py +++ b/src/_pytest/assertion/__init__.py @@ -2,7 +2,6 @@ support for presenting detailed information in failing assertions. """ import sys -from typing import Optional from _pytest.assertion import rewrite from _pytest.assertion import truncate @@ -53,9 +52,7 @@ def register_assert_rewrite(*names): importhook = hook break else: - # TODO(typing): Add a protocol for mark_rewrite() and use it - # for importhook and for PytestPluginManager.rewrite_hook. - importhook = DummyRewriteHook() # type: ignore + importhook = DummyRewriteHook() importhook.mark_rewrite(*names) @@ -72,7 +69,7 @@ class AssertionState: def __init__(self, config, mode): self.mode = mode self.trace = config.trace.root.get("assertion") - self.hook = None # type: Optional[rewrite.AssertionRewritingHook] + self.hook = None def install_importhook(config): @@ -111,7 +108,6 @@ def pytest_runtest_setup(item): """ def callbinrepr(op, left, right): - # type: (str, object, object) -> Optional[str] """Call the pytest_assertrepr_compare hook and prepare the result This uses the first result from the hook and then ensures the @@ -137,13 +133,12 @@ def callbinrepr(op, left, right): if item.config.getvalue("assertmode") == "rewrite": res = res.replace("%", "%%") return res - return None util._reprcompare = callbinrepr if item.ihook.pytest_assertion_pass.get_hookimpls(): - def call_assertion_pass_hook(lineno, orig, expl): + def call_assertion_pass_hook(lineno, expl, orig): item.ihook.pytest_assertion_pass( item=item, lineno=lineno, orig=orig, expl=expl ) diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index c225eff5fb5..7627cb968c3 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -2,7 +2,6 @@ import ast import errno import functools -import importlib.abc import importlib.machinery import importlib.util import io @@ -17,7 +16,6 @@ from typing import List from typing import Optional from typing import Set -from typing import Tuple import atomicwrites @@ -36,7 +34,7 @@ PYC_TAIL = "." + PYTEST_TAG + PYC_EXT -class AssertionRewritingHook(importlib.abc.MetaPathFinder): +class AssertionRewritingHook: """PEP302/PEP451 import hook which rewrites asserts.""" def __init__(self, config): @@ -46,13 +44,13 @@ def __init__(self, config): except ValueError: self.fnpats = ["test_*.py", "*_test.py"] self.session = None - self._rewritten_names = set() # type: Set[str] - self._must_rewrite = set() # type: Set[str] + self._rewritten_names = set() + self._must_rewrite = set() # flag to guard against trying to rewrite a pyc file while we are already writing another pyc file, # which might result in infinite recursion (#3506) self._writing_pyc = False self._basenames_to_check_rewrite = {"conftest"} - self._marked_for_rewrite_cache = {} # type: Dict[str, bool] + self._marked_for_rewrite_cache = {} self._session_paths_checked = False def set_session(self, session): @@ -201,7 +199,7 @@ def _should_rewrite(self, name, fn, state): return self._is_marked_for_rewrite(name, state) - def _is_marked_for_rewrite(self, name: str, state): + def _is_marked_for_rewrite(self, name, state): try: return self._marked_for_rewrite_cache[name] except KeyError: @@ -216,7 +214,7 @@ def _is_marked_for_rewrite(self, name: str, state): self._marked_for_rewrite_cache[name] = False return False - def mark_rewrite(self, *names: str) -> None: + def mark_rewrite(self, *names): """Mark import names as needing to be rewritten. The named module or package as well as any nested modules will @@ -383,7 +381,6 @@ def _format_boolop(explanations, is_or): def _call_reprcompare(ops, results, expls, each_obj): - # type: (Tuple[str, ...], Tuple[bool, ...], Tuple[str, ...], Tuple[object, ...]) -> str for i, res, expl in zip(range(len(ops)), results, expls): try: done = not res @@ -399,13 +396,11 @@ def _call_reprcompare(ops, results, expls, each_obj): def _call_assertion_pass(lineno, orig, expl): - # type: (int, str, str) -> None if util._assertion_pass is not None: - util._assertion_pass(lineno, orig, expl) + util._assertion_pass(lineno=lineno, orig=orig, expl=expl) def _check_if_assertion_pass_impl(): - # type: () -> bool """Checks if any plugins implement the pytest_assertion_pass hook in order not to generate explanation unecessarily (might be expensive)""" return True if util._assertion_pass else False @@ -579,7 +574,7 @@ def __init__(self, module_path, config, source): def _assert_expr_to_lineno(self): return _get_assertion_exprs(self.source) - def run(self, mod: ast.Module) -> None: + def run(self, mod): """Find all assert statements in *mod* and rewrite them.""" if not mod.body: # Nothing to do. @@ -621,12 +616,12 @@ def run(self, mod: ast.Module) -> None: ] mod.body[pos:pos] = imports # Collect asserts. - nodes = [mod] # type: List[ast.AST] + nodes = [mod] while nodes: node = nodes.pop() for name, field in ast.iter_fields(node): if isinstance(field, list): - new = [] # type: List + new = [] for i, child in enumerate(field): if isinstance(child, ast.Assert): # Transform assert. @@ -700,7 +695,7 @@ def push_format_context(self): .explanation_param(). """ - self.explanation_specifiers = {} # type: Dict[str, ast.expr] + self.explanation_specifiers = {} self.stack.append(self.explanation_specifiers) def pop_format_context(self, expl_expr): @@ -743,8 +738,7 @@ def visit_Assert(self, assert_): from _pytest.warning_types import PytestAssertRewriteWarning import warnings - # Ignore type: typeshed bug https://github.com/python/typeshed/pull/3121 - warnings.warn_explicit( # type: ignore + warnings.warn_explicit( PytestAssertRewriteWarning( "assertion is always true, perhaps remove parentheses?" ), @@ -753,15 +747,15 @@ def visit_Assert(self, assert_): lineno=assert_.lineno, ) - self.statements = [] # type: List[ast.stmt] - self.variables = [] # type: List[str] + self.statements = [] + self.variables = [] self.variable_counter = itertools.count() if self.enable_assertion_pass_hook: - self.format_variables = [] # type: List[str] + self.format_variables = [] - self.stack = [] # type: List[Dict[str, ast.expr]] - self.expl_stmts = [] # type: List[ast.stmt] + self.stack = [] + self.expl_stmts = [] self.push_format_context() # Rewrite assert into a bunch of statements. top_condition, explanation = self.visit(assert_.test) @@ -899,7 +893,7 @@ def visit_BoolOp(self, boolop): # Process each operand, short-circuiting if needed. for i, v in enumerate(boolop.values): if i: - fail_inner = [] # type: List[ast.stmt] + fail_inner = [] # cond is set in a prior loop iteration below self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa self.expl_stmts = fail_inner @@ -910,10 +904,10 @@ def visit_BoolOp(self, boolop): call = ast.Call(app, [expl_format], []) self.expl_stmts.append(ast.Expr(call)) if i < levels: - cond = res # type: ast.expr + cond = res if is_or: cond = ast.UnaryOp(ast.Not(), cond) - inner = [] # type: List[ast.stmt] + inner = [] self.statements.append(ast.If(cond, inner, [])) self.statements = body = inner self.statements = save @@ -979,7 +973,7 @@ def visit_Attribute(self, attr): expl = pat % (res_expl, res_expl, value_expl, attr.attr) return res, expl - def visit_Compare(self, comp: ast.Compare): + def visit_Compare(self, comp): self.push_format_context() left_res, left_expl = self.visit(comp.left) if isinstance(comp.left, (ast.Compare, ast.BoolOp)): @@ -1012,7 +1006,7 @@ def visit_Compare(self, comp: ast.Compare): ast.Tuple(results, ast.Load()), ) if len(comp.ops) > 1: - res = ast.BoolOp(ast.And(), load_names) # type: ast.expr + res = ast.BoolOp(ast.And(), load_names) else: res = load_names[0] return res, self.explanation_param(self.pop_format_context(expl_call)) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 11c7bdf6f85..732194ec222 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -1,9 +1,6 @@ """Utilities for assertion debugging""" import pprint from collections.abc import Sequence -from typing import Callable -from typing import List -from typing import Optional import _pytest._code from _pytest import outcomes @@ -13,11 +10,11 @@ # interpretation code and assertion rewriter to detect this plugin was # loaded and in turn call the hooks defined here as part of the # DebugInterpreter. -_reprcompare = None # type: Optional[Callable[[str, object, object], Optional[str]]] +_reprcompare = None # Works similarly as _reprcompare attribute. Is populated with the hook call # when pytest_runtest_setup is called. -_assertion_pass = None # type: Optional[Callable[[int, str, str], None]] +_assertion_pass = None def format_explanation(explanation): @@ -180,7 +177,7 @@ def _diff_text(left, right, verbose=0): """ from difflib import ndiff - explanation = [] # type: List[str] + explanation = [] def escape_for_readable_diff(binary_text): """ @@ -238,7 +235,7 @@ def _compare_eq_verbose(left, right): left_lines = repr(left).splitlines(keepends) right_lines = repr(right).splitlines(keepends) - explanation = [] # type: List[str] + explanation = [] explanation += ["-" + line for line in left_lines] explanation += ["+" + line for line in right_lines] @@ -262,7 +259,7 @@ def _compare_eq_iterable(left, right, verbose=0): def _compare_eq_sequence(left, right, verbose=0): comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes) - explanation = [] # type: List[str] + explanation = [] len_left = len(left) len_right = len(right) for i in range(min(len_left, len_right)): @@ -330,7 +327,7 @@ def _compare_eq_set(left, right, verbose=0): def _compare_eq_dict(left, right, verbose=0): - explanation = [] # type: List[str] + explanation = [] set_left = set(left) set_right = set(right) common = set_left.intersection(set_right) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 3164c81ba93..e39c63c4b52 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -9,15 +9,6 @@ import warnings from functools import lru_cache from pathlib import Path -from types import TracebackType -from typing import Any -from typing import Callable -from typing import Dict -from typing import List -from typing import Optional -from typing import Sequence -from typing import Set -from typing import Tuple import attr import py @@ -41,10 +32,6 @@ from _pytest.outcomes import Skipped from _pytest.warning_types import PytestConfigWarning -if False: # TYPE_CHECKING - from typing import Type - - hookimpl = HookimplMarker("pytest") hookspec = HookspecMarker("pytest") @@ -53,7 +40,7 @@ class ConftestImportFailure(Exception): def __init__(self, path, excinfo): Exception.__init__(self, path, excinfo) self.path = path - self.excinfo = excinfo # type: Tuple[Type[Exception], Exception, TracebackType] + self.excinfo = excinfo def main(args=None, plugins=None): @@ -250,18 +237,14 @@ class PytestPluginManager(PluginManager): def __init__(self): super().__init__("pytest") - # The objects are module objects, only used generically. - self._conftest_plugins = set() # type: Set[object] + self._conftest_plugins = set() # state related to local conftest plugins - # Maps a py.path.local to a list of module objects. - self._dirpath2confmods = {} # type: Dict[Any, List[object]] - # Maps a py.path.local to a module object. - self._conftestpath2mod = {} # type: Dict[Any, object] + self._dirpath2confmods = {} + self._conftestpath2mod = {} self._confcutdir = None self._noconftest = False - # Set of py.path.local's. - self._duplicatepaths = set() # type: Set[Any] + self._duplicatepaths = set() self.add_hookspecs(_pytest.hookspec) self.register(self) @@ -673,7 +656,7 @@ class InvocationParams: args = attr.ib() plugins = attr.ib() - dir = attr.ib(type=Path) + dir = attr.ib() def __init__(self, pluginmanager, *, invocation_params=None): from .argparsing import Parser, FILE_OR_DIR @@ -694,10 +677,10 @@ def __init__(self, pluginmanager, *, invocation_params=None): self.pluginmanager = pluginmanager self.trace = self.pluginmanager.trace.root.get("config") self.hook = self.pluginmanager.hook - self._inicache = {} # type: Dict[str, Any] - self._override_ini = () # type: Sequence[str] - self._opt2dest = {} # type: Dict[str, str] - self._cleanup = [] # type: List[Callable[[], None]] + self._inicache = {} + self._override_ini = () + self._opt2dest = {} + self._cleanup = [] self.pluginmanager.register(self, "pytestconfig") self._configured = False self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser)) @@ -798,7 +781,7 @@ def _processopt(self, opt): def pytest_load_initial_conftests(self, early_config): self.pluginmanager._set_initial_conftests(early_config.known_args_namespace) - def _initini(self, args) -> None: + def _initini(self, args): ns, unknown_args = self._parser.parse_known_and_unknown_args( args, namespace=copy.copy(self.option) ) @@ -899,7 +882,8 @@ def _preparse(self, args, addopts=True): self.hook.pytest_load_initial_conftests( early_config=self, args=args, parser=self._parser ) - except ConftestImportFailure as e: + except ConftestImportFailure: + e = sys.exc_info()[1] if ns.help or ns.version: # we don't want to prevent --help/--version to work # so just let is pass and print a warning at the end @@ -965,7 +949,7 @@ def addinivalue_line(self, name, line): assert isinstance(x, list) x.append(line) # modifies the cached list inline - def getini(self, name: str): + def getini(self, name): """ return configuration value from an :ref:`ini file `. If the specified name hasn't been registered through a prior :py:func:`parser.addini <_pytest.config.Parser.addini>` @@ -976,7 +960,7 @@ def getini(self, name: str): self._inicache[name] = val = self._getini(name) return val - def _getini(self, name: str) -> Any: + def _getini(self, name): try: description, type, default = self._parser._inidict[name] except KeyError: @@ -1021,7 +1005,7 @@ def _getconftest_pathlist(self, name, path): values.append(relroot) return values - def _get_override_ini_value(self, name: str) -> Optional[str]: + def _get_override_ini_value(self, name): value = None # override_ini is a list of "ini=value" options # always use the last item if multiple values are set for same ini-name, @@ -1036,7 +1020,7 @@ def _get_override_ini_value(self, name: str) -> Optional[str]: value = user_ini_value return value - def getoption(self, name: str, default=notset, skip: bool = False): + def getoption(self, name, default=notset, skip=False): """ return command line option value. :arg name: name of the option. You may also specify diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 9b526ff3e1f..8994ff7d9d7 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -2,11 +2,6 @@ import sys import warnings from gettext import gettext -from typing import Any -from typing import Dict -from typing import List -from typing import Optional -from typing import Tuple import py @@ -26,12 +21,12 @@ class Parser: def __init__(self, usage=None, processopt=None): self._anonymous = OptionGroup("custom options", parser=self) - self._groups = [] # type: List[OptionGroup] + self._groups = [] self._processopt = processopt self._usage = usage - self._inidict = {} # type: Dict[str, Tuple[str, Optional[str], Any]] - self._ininames = [] # type: List[str] - self.extra_info = {} # type: Dict[str, Any] + self._inidict = {} + self._ininames = [] + self.extra_info = {} def processoption(self, option): if self._processopt: @@ -85,7 +80,7 @@ def parse(self, args, namespace=None): args = [str(x) if isinstance(x, py.path.local) else x for x in args] return self.optparser.parse_args(args, namespace=namespace) - def _getparser(self) -> "MyOptionParser": + def _getparser(self): from _pytest._argcomplete import filescompleter optparser = MyOptionParser(self, self.extra_info, prog=self.prog) @@ -99,10 +94,7 @@ def _getparser(self) -> "MyOptionParser": a = option.attrs() arggroup.add_argument(*n, **a) # bash like autocompletion for dirs (appending '/') - # Type ignored because typeshed doesn't know about argcomplete. - optparser.add_argument( # type: ignore - 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): @@ -111,15 +103,13 @@ def parse_setoption(self, args, option, namespace=None): setattr(option, name, value) return getattr(parsedoption, FILE_OR_DIR) - def parse_known_args(self, args, namespace=None) -> argparse.Namespace: + def parse_known_args(self, args, namespace=None): """parses and returns a namespace object with known arguments at this point. """ return self.parse_known_and_unknown_args(args, namespace=namespace)[0] - def parse_known_and_unknown_args( - self, args, namespace=None - ) -> Tuple[argparse.Namespace, List[str]]: + def parse_known_and_unknown_args(self, args, namespace=None): """parses and returns a namespace object with known arguments, and the remaining arguments unknown at this point. """ @@ -173,8 +163,8 @@ class Argument: def __init__(self, *names, **attrs): """store parms in private vars for use in add_argument""" self._attrs = attrs - self._short_opts = [] # type: List[str] - self._long_opts = [] # type: List[str] + self._short_opts = [] + self._long_opts = [] self.dest = attrs.get("dest") if "%default" in (attrs.get("help") or ""): warnings.warn( @@ -278,8 +268,8 @@ def _set_opt_strings(self, opts): ) self._long_opts.append(opt) - def __repr__(self) -> str: - args = [] # type: List[str] + def __repr__(self): + args = [] if self._short_opts: args += ["_short_opts: " + repr(self._short_opts)] if self._long_opts: @@ -296,7 +286,7 @@ class OptionGroup: def __init__(self, name, description="", parser=None): self.name = name self.description = description - self.options = [] # type: List[Argument] + self.options = [] self.parser = parser def addoption(self, *optnames, **attrs): @@ -415,12 +405,6 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter): - cache result on action object as this is called at least 2 times """ - def __init__(self, *args, **kwargs): - """Use more accurate terminal width via pylib.""" - if "width" not in kwargs: - kwargs["width"] = py.io.get_terminal_width() - super().__init__(*args, **kwargs) - def _format_action_invocation(self, action): orgstr = argparse.HelpFormatter._format_action_invocation(self, action) if orgstr and orgstr[0] != "-": # only optional arguments @@ -437,7 +421,7 @@ def _format_action_invocation(self, action): option_map = getattr(action, "map_long_option", {}) if option_map is None: option_map = {} - short_long = {} # type: Dict[str, str] + short_long = {} for option in options: if len(option) == 2 or option[2] == " ": continue diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index f06c9cfffb1..ec991316af1 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -1,15 +1,10 @@ import os -from typing import List -from typing import Optional import py from .exceptions import UsageError from _pytest.outcomes import fail -if False: - from . import Config # noqa: F401 - def exists(path, ignore=EnvironmentError): try: @@ -107,12 +102,7 @@ def get_dir_from_path(path): CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead." -def determine_setup( - inifile: str, - args: List[str], - rootdir_cmd_arg: Optional[str] = None, - config: Optional["Config"] = None, -): +def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None): dirs = get_dirs_from_args(args) if inifile: iniconfig = py.iniconfig.IniConfig(inifile) diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 5186067ef05..c0690893207 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -29,8 +29,3 @@ "--result-log is deprecated and scheduled for removal in pytest 6.0.\n" "See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information." ) - -FIXTURE_POSITIONAL_ARGUMENTS = PytestDeprecationWarning( - "Passing arguments to pytest.fixture() as positional arguments is deprecated - pass them " - "as a keyword argument instead." -) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index b4b10fcd249..e78ed011ec2 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -2,7 +2,6 @@ import inspect import itertools import sys -import warnings from collections import defaultdict from collections import deque from collections import OrderedDict @@ -28,7 +27,6 @@ from _pytest.compat import is_generator from _pytest.compat import NOTSET from _pytest.compat import safe_getattr -from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS from _pytest.outcomes import fail from _pytest.outcomes import TEST_OUTCOME @@ -60,6 +58,7 @@ def pytest_sessionstart(session): scopename2class = {} # type: Dict[str, Type[nodes.Node]] + scope2props = dict(session=()) # type: Dict[str, Tuple[str, ...]] scope2props["package"] = ("fspath",) scope2props["module"] = ("fspath", "module") @@ -793,25 +792,6 @@ def _teardown_yield_fixture(fixturefunc, it): ) -def _eval_scope_callable(scope_callable, fixture_name, config): - try: - result = scope_callable(fixture_name=fixture_name, config=config) - except Exception: - raise TypeError( - "Error evaluating {} while defining fixture '{}'.\n" - "Expected a function with the signature (*, fixture_name, config)".format( - scope_callable, fixture_name - ) - ) - if not isinstance(result, str): - fail( - "Expected {} to return a 'str' while defining fixture '{}', but it returned:\n" - "{!r}".format(scope_callable, fixture_name, result), - pytrace=False, - ) - return result - - class FixtureDef: """ A container for a factory definition. """ @@ -831,8 +811,6 @@ def __init__( self.has_location = baseid is not None self.func = func self.argname = argname - if callable(scope): - scope = _eval_scope_callable(scope, argname, fixturemanager.config) self.scope = scope self.scopenum = scope2index( scope or "function", @@ -1017,57 +995,7 @@ def __call__(self, function): return function -FIXTURE_ARGS_ORDER = ("scope", "params", "autouse", "ids", "name") - - -def _parse_fixture_args(callable_or_scope, *args, **kwargs): - arguments = { - "scope": "function", - "params": None, - "autouse": False, - "ids": None, - "name": None, - } - kwargs = { - key: value for key, value in kwargs.items() if arguments.get(key) != value - } - - fixture_function = None - if isinstance(callable_or_scope, str): - args = list(args) - args.insert(0, callable_or_scope) - else: - fixture_function = callable_or_scope - - positionals = set() - for positional, argument_name in zip(args, FIXTURE_ARGS_ORDER): - arguments[argument_name] = positional - positionals.add(argument_name) - - duplicated_kwargs = {kwarg for kwarg in kwargs.keys() if kwarg in positionals} - if duplicated_kwargs: - raise TypeError( - "The fixture arguments are defined as positional and keyword: {}. " - "Use only keyword arguments.".format(", ".join(duplicated_kwargs)) - ) - - if positionals: - warnings.warn(FIXTURE_POSITIONAL_ARGUMENTS, stacklevel=2) - - arguments.update(kwargs) - - return fixture_function, arguments - - -def fixture( - callable_or_scope=None, - *args, - scope="function", - params=None, - autouse=False, - ids=None, - name=None -): +def fixture(scope="function", params=None, autouse=False, ids=None, name=None): """Decorator to mark a fixture factory function. This decorator can be used, with or without parameters, to define a @@ -1113,55 +1041,21 @@ def fixture( ``fixture_`` and then use ``@pytest.fixture(name='')``. """ - fixture_function, arguments = _parse_fixture_args( - callable_or_scope, - *args, - scope=scope, - params=params, - autouse=autouse, - ids=ids, - name=name - ) - scope = arguments.get("scope") - params = arguments.get("params") - autouse = arguments.get("autouse") - ids = arguments.get("ids") - name = arguments.get("name") - - if fixture_function and params is None and autouse is False: + if callable(scope) and params is None and autouse is False: # direct decoration - return FixtureFunctionMarker(scope, params, autouse, name=name)( - fixture_function - ) - + return FixtureFunctionMarker("function", params, autouse, name=name)(scope) if params is not None and not isinstance(params, (list, tuple)): params = list(params) return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name) -def yield_fixture( - callable_or_scope=None, - *args, - scope="function", - params=None, - autouse=False, - ids=None, - name=None -): +def yield_fixture(scope="function", params=None, autouse=False, ids=None, name=None): """ (return a) decorator to mark a yield-fixture factory function. .. deprecated:: 3.0 Use :py:func:`pytest.fixture` directly instead. """ - return fixture( - callable_or_scope, - *args, - scope=scope, - params=params, - autouse=autouse, - ids=ids, - name=name - ) + return fixture(scope=scope, params=params, autouse=autouse, ids=ids, name=name) defaultfuncargprefixmarker = fixture() diff --git a/src/_pytest/mark/evaluate.py b/src/_pytest/mark/evaluate.py index b9f2d61f835..898278e30b3 100644 --- a/src/_pytest/mark/evaluate.py +++ b/src/_pytest/mark/evaluate.py @@ -51,8 +51,6 @@ def istrue(self): except TEST_OUTCOME: self.exc = sys.exc_info() if isinstance(self.exc[1], SyntaxError): - # TODO: Investigate why SyntaxError.offset is Optional, and if it can be None here. - assert self.exc[1].offset is not None msg = [" " * (self.exc[1].offset + 4) + "^"] msg.append("SyntaxError: invalid syntax") else: diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index f8cf55b4cb6..332c86bdecf 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -292,7 +292,7 @@ def test_function(): _config = None _markers = set() # type: Set[str] - def __getattr__(self, name: str) -> MarkDecorator: + def __getattr__(self, name): if name[0] == "_": raise AttributeError("Marker name must NOT start with underscore") diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index e6dee15470c..c0a88fbbcec 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -1,26 +1,14 @@ import os import warnings from functools import lru_cache -from typing import Any -from typing import Dict -from typing import List -from typing import Set -from typing import Tuple -from typing import Union import py import _pytest._code from _pytest.compat import getfslineno -from _pytest.mark.structures import Mark -from _pytest.mark.structures import MarkDecorator from _pytest.mark.structures import NodeKeywords from _pytest.outcomes import fail -if False: # TYPE_CHECKING - # Imported here due to circular import. - from _pytest.fixtures import FixtureDef - SEP = "/" tracebackcutdir = py.path.local(_pytest.__file__).dirpath() @@ -90,13 +78,13 @@ def __init__( self.keywords = NodeKeywords(self) #: the marker objects belonging to this node - self.own_markers = [] # type: List[Mark] + self.own_markers = [] #: allow adding of extra keywords to use for matching - self.extra_keyword_matches = set() # type: Set[str] + self.extra_keyword_matches = set() # used for storing artificial fixturedefs for direct parametrization - self._name2pseudofixturedef = {} # type: Dict[str, FixtureDef] + self._name2pseudofixturedef = {} if nodeid is not None: assert "::()" not in nodeid @@ -139,8 +127,7 @@ def warn(self, warning): ) ) path, lineno = get_fslocation_from_item(self) - # Type ignored: https://github.com/python/typeshed/pull/3121 - warnings.warn_explicit( # type: ignore + warnings.warn_explicit( warning, category=None, filename=str(path), @@ -173,9 +160,7 @@ def listchain(self): chain.reverse() return chain - def add_marker( - self, marker: Union[str, MarkDecorator], append: bool = True - ) -> None: + def add_marker(self, marker, append=True): """dynamically add a marker object to the node. :type marker: ``str`` or ``pytest.mark.*`` object @@ -183,19 +168,17 @@ def add_marker( ``append=True`` whether to append the marker, if ``False`` insert at position ``0``. """ - from _pytest.mark import MARK_GEN + from _pytest.mark import MarkDecorator, MARK_GEN - if isinstance(marker, MarkDecorator): - marker_ = marker - elif isinstance(marker, str): - marker_ = getattr(MARK_GEN, marker) - else: + if isinstance(marker, str): + marker = getattr(MARK_GEN, marker) + elif not isinstance(marker, MarkDecorator): raise ValueError("is not a string or pytest.mark.* Marker") - self.keywords[marker_.name] = marker + self.keywords[marker.name] = marker if append: - self.own_markers.append(marker_.mark) + self.own_markers.append(marker.mark) else: - self.own_markers.insert(0, marker_.mark) + self.own_markers.insert(0, marker.mark) def iter_markers(self, name=None): """ @@ -228,7 +211,7 @@ def get_closest_marker(self, name, default=None): def listextrakeywords(self): """ Return a set of all extra keywords in self and any parents.""" - extra_keywords = set() # type: Set[str] + extra_keywords = set() for item in self.listchain(): extra_keywords.update(item.extra_keyword_matches) return extra_keywords @@ -256,8 +239,7 @@ def _prunetraceback(self, excinfo): pass def _repr_failure_py(self, excinfo, style=None): - # Type ignored: see comment where fail.Exception is defined. - if excinfo.errisinstance(fail.Exception): # type: ignore + if excinfo.errisinstance(fail.Exception): if not excinfo.value.pytrace: return str(excinfo.value) fm = self.session._fixturemanager @@ -401,13 +383,13 @@ class Item(Node): def __init__(self, name, parent=None, config=None, session=None, nodeid=None): super().__init__(name, parent, config, session, nodeid=nodeid) - self._report_sections = [] # type: List[Tuple[str, str, str]] + self._report_sections = [] #: user properties is a list of tuples (name, value) that holds user #: defined properties for this test. - self.user_properties = [] # type: List[Tuple[str, Any]] + self.user_properties = [] - def add_report_section(self, when: str, key: str, content: str) -> None: + def add_report_section(self, when, key, content): """ Adds a new report section, similar to what's done internally to add stdout and stderr captured output:: diff --git a/src/_pytest/pastebin.py b/src/_pytest/pastebin.py index 77b4e2621eb..91aa5f1fdcb 100644 --- a/src/_pytest/pastebin.py +++ b/src/_pytest/pastebin.py @@ -59,25 +59,20 @@ def create_new_paste(contents): Creates a new paste using bpaste.net service. :contents: paste contents as utf-8 encoded bytes - :returns: url to the pasted contents or error message + :returns: url to the pasted contents """ import re from urllib.request import urlopen from urllib.parse import urlencode - params = {"code": contents, "lexer": "text", "expiry": "1week"} + params = {"code": contents, "lexer": "python3", "expiry": "1week"} url = "https://bpaste.net" - try: - response = ( - urlopen(url, data=urlencode(params).encode("ascii")).read().decode("utf-8") - ) - except OSError as exc_info: # urllib errors - return "bad response: %s" % exc_info - m = re.search(r'href="/raw/(\w+)"', response) + response = urlopen(url, data=urlencode(params).encode("ascii")).read() + m = re.search(r'href="/raw/(\w+)"', response.decode("utf-8")) if m: return "{}/show/{}".format(url, m.group(1)) else: - return "bad response: invalid format ('" + response + "')" + return "bad response: " + response.decode("utf-8") def pytest_terminal_summary(terminalreporter): diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index d8030b92649..56aea248d06 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -1,6 +1,5 @@ from pprint import pprint from typing import Optional -from typing import Union import py @@ -268,8 +267,7 @@ def from_item_and_call(cls, item, call): if not isinstance(excinfo, ExceptionInfo): outcome = "failed" longrepr = excinfo - # Type ignored -- see comment where skip.Exception is defined. - elif excinfo.errisinstance(skip.Exception): # type: ignore + elif excinfo.errisinstance(skip.Exception): outcome = "skipped" r = excinfo._getreprcrash() longrepr = (str(r.path), r.lineno, r.message) @@ -433,7 +431,7 @@ def deserialize_repr_entry(entry_data): reprlocals=reprlocals, filelocrepr=reprfileloc, style=data["style"], - ) # type: Union[ReprEntry, ReprEntryNative] + ) elif entry_type == "ReprEntryNative": reprentry = ReprEntryNative(data["lines"]) else: diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 7d8b74a806e..8aae163c3d0 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -3,10 +3,6 @@ import os import sys from time import time -from typing import Callable -from typing import Dict -from typing import List -from typing import Tuple import attr @@ -14,14 +10,10 @@ from .reports import CollectReport from .reports import TestReport from _pytest._code.code import ExceptionInfo -from _pytest.nodes import Node from _pytest.outcomes import Exit from _pytest.outcomes import Skipped from _pytest.outcomes import TEST_OUTCOME -if False: # TYPE_CHECKING - from typing import Type - # # pytest plugin hooks @@ -126,7 +118,6 @@ def pytest_runtest_call(item): except Exception: # Store trace info to allow postmortem debugging type, value, tb = sys.exc_info() - assert tb is not None tb = tb.tb_next # Skip *this* frame sys.last_type = type sys.last_value = value @@ -194,7 +185,7 @@ def check_interactive_exception(call, report): def call_runtest_hook(item, when, **kwds): hookname = "pytest_runtest_" + when ihook = getattr(item.ihook, hookname) - reraise = (Exit,) # type: Tuple[Type[BaseException], ...] + reraise = (Exit,) if not item.config.getoption("usepdb", False): reraise += (KeyboardInterrupt,) return CallInfo.from_call( @@ -261,8 +252,7 @@ def pytest_make_collect_report(collector): skip_exceptions = [Skipped] unittest = sys.modules.get("unittest") if unittest is not None: - # Type ignored because unittest is loaded dynamically. - skip_exceptions.append(unittest.SkipTest) # type: ignore + skip_exceptions.append(unittest.SkipTest) if call.excinfo.errisinstance(tuple(skip_exceptions)): outcome = "skipped" r = collector._repr_failure_py(call.excinfo, "line").reprcrash @@ -276,7 +266,7 @@ def pytest_make_collect_report(collector): rep = CollectReport( collector.nodeid, outcome, longrepr, getattr(call, "result", None) ) - rep.call = call # type: ignore # see collect_one_node + rep.call = call # see collect_one_node return rep @@ -284,8 +274,8 @@ class SetupState: """ shared state for setting up/tearing down test items or collectors. """ def __init__(self): - self.stack = [] # type: List[Node] - self._finalizers = {} # type: Dict[Node, List[Callable[[], None]]] + self.stack = [] + self._finalizers = {} def addfinalizer(self, finalizer, colitem): """ attach a finalizer to the given colitem. """ @@ -312,7 +302,6 @@ def _callfinalizers(self, colitem): exc = sys.exc_info() if exc: _, val, tb = exc - assert val is not None raise val.with_traceback(tb) def _teardown_with_finalization(self, colitem): @@ -346,7 +335,6 @@ def _teardown_towards(self, needed_collectors): exc = sys.exc_info() if exc: _, val, tb = exc - assert val is not None raise val.with_traceback(tb) def prepare(self, colitem): diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index f4dbfdf0977..43a05e7bbb7 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -2217,68 +2217,6 @@ def test_1(arg): ["*ScopeMismatch*You tried*function*session*request*"] ) - def test_dynamic_scope(self, testdir): - testdir.makeconftest( - """ - import pytest - - - def pytest_addoption(parser): - parser.addoption("--extend-scope", action="store_true", default=False) - - - def dynamic_scope(fixture_name, config): - if config.getoption("--extend-scope"): - return "session" - return "function" - - - @pytest.fixture(scope=dynamic_scope) - def dynamic_fixture(calls=[]): - calls.append("call") - return len(calls) - - """ - ) - - testdir.makepyfile( - """ - def test_first(dynamic_fixture): - assert dynamic_fixture == 1 - - - def test_second(dynamic_fixture): - assert dynamic_fixture == 2 - - """ - ) - - reprec = testdir.inline_run() - reprec.assertoutcome(passed=2) - - reprec = testdir.inline_run("--extend-scope") - reprec.assertoutcome(passed=1, failed=1) - - def test_dynamic_scope_bad_return(self, testdir): - testdir.makepyfile( - """ - import pytest - - def dynamic_scope(**_): - return "wrong-scope" - - @pytest.fixture(scope=dynamic_scope) - def fixture(): - pass - - """ - ) - result = testdir.runpytest() - result.stdout.fnmatch_lines( - "Fixture 'fixture' from test_dynamic_scope_bad_return.py " - "got an unexpected scope value 'wrong-scope'" - ) - def test_register_only_with_mark(self, testdir): testdir.makeconftest( """ @@ -4106,43 +4044,12 @@ def test_fixture_named_request(testdir): ) -def test_fixture_duplicated_arguments(testdir): - """Raise error if there are positional and keyword arguments for the same parameter (#1682).""" - with pytest.raises(TypeError) as excinfo: - - @pytest.fixture("session", scope="session") - def arg(arg): - pass - - assert ( - str(excinfo.value) - == "The fixture arguments are defined as positional and keyword: scope. " - "Use only keyword arguments." - ) - - -def test_fixture_with_positionals(testdir): - """Raise warning, but the positionals should still works (#1682).""" - from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS - - with pytest.warns(pytest.PytestDeprecationWarning) as warnings: - - @pytest.fixture("function", [0], True) - def fixture_with_positionals(): - pass - - assert str(warnings[0].message) == str(FIXTURE_POSITIONAL_ARGUMENTS) - - assert fixture_with_positionals._pytestfixturefunction.scope == "function" - assert fixture_with_positionals._pytestfixturefunction.params == (0,) - assert fixture_with_positionals._pytestfixturefunction.autouse - - def test_indirect_fixture_does_not_break_scope(testdir): """Ensure that fixture scope is respected when using indirect fixtures (#570)""" testdir.makepyfile( """ import pytest + instantiated = [] @pytest.fixture(scope="session") diff --git a/testing/test_config.py b/testing/test_config.py index 71dae5c4cdb..fc3659d2ae1 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1194,21 +1194,6 @@ def pytest_addoption(parser): assert result.ret == ExitCode.USAGE_ERROR -def test_help_formatter_uses_py_get_terminal_width(testdir, monkeypatch): - from _pytest.config.argparsing import DropShorterLongHelpFormatter - - monkeypatch.setenv("COLUMNS", "90") - formatter = DropShorterLongHelpFormatter("prog") - assert formatter._width == 90 - - monkeypatch.setattr("py.io.get_terminal_width", lambda: 160) - formatter = DropShorterLongHelpFormatter("prog") - assert formatter._width == 160 - - formatter = DropShorterLongHelpFormatter("prog", width=42) - assert formatter._width == 42 - - def test_config_does_not_load_blocked_plugin_from_args(testdir): """This tests that pytest's config setup handles "-p no:X".""" p = testdir.makepyfile("def test(capfd): pass") diff --git a/testing/test_pastebin.py b/testing/test_pastebin.py index 86a42f9e8a1..4e8bac56cb2 100644 --- a/testing/test_pastebin.py +++ b/testing/test_pastebin.py @@ -82,47 +82,6 @@ class TestPaste: def pastebin(self, request): return request.config.pluginmanager.getplugin("pastebin") - @pytest.fixture - def mocked_urlopen_fail(self, monkeypatch): - """ - monkeypatch the actual urlopen call to emulate a HTTP Error 400 - """ - calls = [] - - import urllib.error - import urllib.request - - def mocked(url, data): - calls.append((url, data)) - raise urllib.error.HTTPError(url, 400, "Bad request", None, None) - - monkeypatch.setattr(urllib.request, "urlopen", mocked) - return calls - - @pytest.fixture - def mocked_urlopen_invalid(self, monkeypatch): - """ - monkeypatch the actual urlopen calls done by the internal plugin - function that connects to bpaste service, but return a url in an - unexpected format - """ - calls = [] - - def mocked(url, data): - calls.append((url, data)) - - class DummyFile: - def read(self): - # part of html of a normal response - return b'View raw.' - - return DummyFile() - - import urllib.request - - monkeypatch.setattr(urllib.request, "urlopen", mocked) - return calls - @pytest.fixture def mocked_urlopen(self, monkeypatch): """ @@ -146,26 +105,13 @@ def read(self): monkeypatch.setattr(urllib.request, "urlopen", mocked) return calls - def test_pastebin_invalid_url(self, pastebin, mocked_urlopen_invalid): - result = pastebin.create_new_paste(b"full-paste-contents") - assert ( - result - == "bad response: invalid format ('View raw.')" - ) - assert len(mocked_urlopen_invalid) == 1 - - def test_pastebin_http_error(self, pastebin, mocked_urlopen_fail): - result = pastebin.create_new_paste(b"full-paste-contents") - assert result == "bad response: HTTP Error 400: Bad request" - assert len(mocked_urlopen_fail) == 1 - def test_create_new_paste(self, pastebin, mocked_urlopen): result = pastebin.create_new_paste(b"full-paste-contents") assert result == "https://bpaste.net/show/3c0c6750bd" assert len(mocked_urlopen) == 1 url, data = mocked_urlopen[0] assert type(data) is bytes - lexer = "text" + lexer = "python3" assert url == "https://bpaste.net" assert "lexer=%s" % lexer in data.decode() assert "code=full-paste-contents" in data.decode() @@ -181,4 +127,4 @@ def response(url, data): monkeypatch.setattr(urllib.request, "urlopen", response) result = pastebin.create_new_paste(b"full-paste-contents") - assert result == "bad response: invalid format ('something bad occurred')" + assert result == "bad response: something bad occurred"