123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473 |
- # -*- coding: utf-8 -*-
- """
- python version compatibility code
- """
- from __future__ import absolute_import
- from __future__ import division
- from __future__ import print_function
- import codecs
- import functools
- import inspect
- import re
- import sys
- from contextlib import contextmanager
- import attr
- import py
- import six
- from six import text_type
- import _pytest
- from _pytest._io.saferepr import saferepr
- from _pytest.outcomes import fail
- from _pytest.outcomes import TEST_OUTCOME
- try:
- import enum
- except ImportError: # pragma: no cover
- # Only available in Python 3.4+ or as a backport
- enum = None
- _PY3 = sys.version_info > (3, 0)
- _PY2 = not _PY3
- if _PY3:
- from inspect import signature, Parameter as Parameter
- else:
- from funcsigs import signature, Parameter as Parameter
- NOTSET = object()
- PY35 = sys.version_info[:2] >= (3, 5)
- PY36 = sys.version_info[:2] >= (3, 6)
- MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
- if _PY3:
- from collections.abc import MutableMapping as MappingMixin
- from collections.abc import Iterable, Mapping, Sequence, Sized
- else:
- # those raise DeprecationWarnings in Python >=3.7
- from collections import MutableMapping as MappingMixin # noqa
- from collections import Iterable, Mapping, Sequence, Sized # noqa
- if sys.version_info >= (3, 4):
- from importlib.util import spec_from_file_location
- else:
- def spec_from_file_location(*_, **__):
- return None
- if sys.version_info >= (3, 8):
- from importlib import metadata as importlib_metadata # noqa
- else:
- import importlib_metadata # noqa
- def _format_args(func):
- return str(signature(func))
- isfunction = inspect.isfunction
- isclass = inspect.isclass
- # used to work around a python2 exception info leak
- exc_clear = getattr(sys, "exc_clear", lambda: None)
- # The type of re.compile objects is not exposed in Python.
- REGEX_TYPE = type(re.compile(""))
- def is_generator(func):
- genfunc = inspect.isgeneratorfunction(func)
- return genfunc and not iscoroutinefunction(func)
- def iscoroutinefunction(func):
- """Return True if func is a decorated coroutine function.
- Note: copied and modified from Python 3.5's builtin couroutines.py to avoid import asyncio directly,
- which in turns also initializes the "logging" module as side-effect (see issue #8).
- """
- return getattr(func, "_is_coroutine", False) or (
- hasattr(inspect, "iscoroutinefunction") and inspect.iscoroutinefunction(func)
- )
- def getlocation(function, curdir):
- function = get_real_func(function)
- fn = py.path.local(inspect.getfile(function))
- lineno = function.__code__.co_firstlineno
- if fn.relto(curdir):
- fn = fn.relto(curdir)
- return "%s:%d" % (fn, lineno + 1)
- def num_mock_patch_args(function):
- """ return number of arguments used up by mock arguments (if any) """
- patchings = getattr(function, "patchings", None)
- if not patchings:
- return 0
- mock_modules = [sys.modules.get("mock"), sys.modules.get("unittest.mock")]
- if any(mock_modules):
- sentinels = [m.DEFAULT for m in mock_modules if m is not None]
- return len(
- [p for p in patchings if not p.attribute_name and p.new in sentinels]
- )
- return len(patchings)
- def getfuncargnames(function, is_method=False, cls=None):
- """Returns the names of a function's mandatory arguments.
- This should return the names of all function arguments that:
- * Aren't bound to an instance or type as in instance or class methods.
- * Don't have default values.
- * Aren't bound with functools.partial.
- * Aren't replaced with mocks.
- The is_method and cls arguments indicate that the function should
- be treated as a bound method even though it's not unless, only in
- the case of cls, the function is a static method.
- @RonnyPfannschmidt: This function should be refactored when we
- revisit fixtures. The fixture mechanism should ask the node for
- the fixture names, and not try to obtain directly from the
- function object well after collection has occurred.
- """
- # The parameters attribute of a Signature object contains an
- # ordered mapping of parameter names to Parameter instances. This
- # creates a tuple of the names of the parameters that don't have
- # defaults.
- try:
- parameters = signature(function).parameters
- except (ValueError, TypeError) as e:
- fail(
- "Could not determine arguments of {!r}: {}".format(function, e),
- pytrace=False,
- )
- arg_names = tuple(
- p.name
- for p in parameters.values()
- if (
- p.kind is Parameter.POSITIONAL_OR_KEYWORD
- or p.kind is Parameter.KEYWORD_ONLY
- )
- and p.default is Parameter.empty
- )
- # If this function should be treated as a bound method even though
- # it's passed as an unbound method or function, remove the first
- # parameter name.
- if is_method or (
- cls and not isinstance(cls.__dict__.get(function.__name__, None), staticmethod)
- ):
- arg_names = arg_names[1:]
- # Remove any names that will be replaced with mocks.
- if hasattr(function, "__wrapped__"):
- arg_names = arg_names[num_mock_patch_args(function) :]
- return arg_names
- @contextmanager
- def dummy_context_manager():
- """Context manager that does nothing, useful in situations where you might need an actual context manager or not
- depending on some condition. Using this allow to keep the same code"""
- yield
- def get_default_arg_names(function):
- # Note: this code intentionally mirrors the code at the beginning of getfuncargnames,
- # to get the arguments which were excluded from its result because they had default values
- return tuple(
- p.name
- for p in signature(function).parameters.values()
- if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY)
- and p.default is not Parameter.empty
- )
- _non_printable_ascii_translate_table = {
- i: u"\\x{:02x}".format(i) for i in range(128) if i not in range(32, 127)
- }
- _non_printable_ascii_translate_table.update(
- {ord("\t"): u"\\t", ord("\r"): u"\\r", ord("\n"): u"\\n"}
- )
- def _translate_non_printable(s):
- return s.translate(_non_printable_ascii_translate_table)
- if _PY3:
- STRING_TYPES = bytes, str
- UNICODE_TYPES = six.text_type
- if PY35:
- def _bytes_to_ascii(val):
- return val.decode("ascii", "backslashreplace")
- else:
- def _bytes_to_ascii(val):
- if val:
- # source: http://goo.gl/bGsnwC
- encoded_bytes, _ = codecs.escape_encode(val)
- return encoded_bytes.decode("ascii")
- else:
- # empty bytes crashes codecs.escape_encode (#1087)
- return ""
- def ascii_escaped(val):
- """If val is pure ascii, returns it as a str(). Otherwise, escapes
- bytes objects into a sequence of escaped bytes:
- b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6'
- and escapes unicode objects into a sequence of escaped unicode
- ids, e.g.:
- '4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944'
- note:
- the obvious "v.decode('unicode-escape')" will return
- valid utf-8 unicode if it finds them in bytes, but we
- want to return escaped bytes for any byte, even if they match
- a utf-8 string.
- """
- if isinstance(val, bytes):
- ret = _bytes_to_ascii(val)
- else:
- ret = val
- return ret
- else:
- STRING_TYPES = six.string_types
- UNICODE_TYPES = six.text_type
- def ascii_escaped(val):
- """In py2 bytes and str are the same type, so return if it's a bytes
- object, return it unchanged if it is a full ascii string,
- otherwise escape it into its binary form.
- If it's a unicode string, change the unicode characters into
- unicode escapes.
- """
- if isinstance(val, bytes):
- try:
- ret = val.decode("utf-8")
- except UnicodeDecodeError:
- ret = val.decode("utf-8", "ignore")
- else:
- ret = val.encode("utf-8", "replace").decode("utf-8")
- return ret
- class _PytestWrapper(object):
- """Dummy wrapper around a function object for internal use only.
- Used to correctly unwrap the underlying function object
- when we are creating fixtures, because we wrap the function object ourselves with a decorator
- to issue warnings when the fixture function is called directly.
- """
- def __init__(self, obj):
- self.obj = obj
- def get_real_func(obj):
- """ gets the real function object of the (possibly) wrapped object by
- functools.wraps or functools.partial.
- """
- start_obj = obj
- for i in range(100):
- # __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function
- # to trigger a warning if it gets called directly instead of by pytest: we don't
- # want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774)
- new_obj = getattr(obj, "__pytest_wrapped__", None)
- if isinstance(new_obj, _PytestWrapper):
- obj = new_obj.obj
- break
- new_obj = getattr(obj, "__wrapped__", None)
- if new_obj is None:
- break
- obj = new_obj
- else:
- raise ValueError(
- ("could not find real function of {start}\nstopped at {current}").format(
- start=saferepr(start_obj), current=saferepr(obj)
- )
- )
- if isinstance(obj, functools.partial):
- obj = obj.func
- return obj
- def get_real_method(obj, holder):
- """
- Attempts to obtain the real function object that might be wrapping ``obj``, while at the same time
- returning a bound method to ``holder`` if the original object was a bound method.
- """
- try:
- is_method = hasattr(obj, "__func__")
- obj = get_real_func(obj)
- except Exception:
- return obj
- if is_method and hasattr(obj, "__get__") and callable(obj.__get__):
- obj = obj.__get__(holder)
- return obj
- def getfslineno(obj):
- # xxx let decorators etc specify a sane ordering
- obj = get_real_func(obj)
- if hasattr(obj, "place_as"):
- obj = obj.place_as
- fslineno = _pytest._code.getfslineno(obj)
- assert isinstance(fslineno[1], int), obj
- return fslineno
- def getimfunc(func):
- try:
- return func.__func__
- except AttributeError:
- return func
- def safe_getattr(object, name, default):
- """ Like getattr but return default upon any Exception or any OutcomeException.
- Attribute access can potentially fail for 'evil' Python objects.
- See issue #214.
- It catches OutcomeException because of #2490 (issue #580), new outcomes are derived from BaseException
- instead of Exception (for more details check #2707)
- """
- try:
- return getattr(object, name, default)
- except TEST_OUTCOME:
- return default
- def safe_isclass(obj):
- """Ignore any exception via isinstance on Python 3."""
- try:
- return isclass(obj)
- except Exception:
- return False
- def _is_unittest_unexpected_success_a_failure():
- """Return if the test suite should fail if an @expectedFailure unittest test PASSES.
- From https://docs.python.org/3/library/unittest.html?highlight=unittest#unittest.TestResult.wasSuccessful:
- Changed in version 3.4: Returns False if there were any
- unexpectedSuccesses from tests marked with the expectedFailure() decorator.
- """
- return sys.version_info >= (3, 4)
- if _PY3:
- def safe_str(v):
- """returns v as string"""
- try:
- return str(v)
- except UnicodeEncodeError:
- return str(v, encoding="utf-8")
- else:
- def safe_str(v):
- """returns v as string, converting to utf-8 if necessary"""
- try:
- return str(v)
- except UnicodeError:
- if not isinstance(v, text_type):
- v = text_type(v)
- errors = "replace"
- return v.encode("utf-8", errors)
- COLLECT_FAKEMODULE_ATTRIBUTES = (
- "Collector",
- "Module",
- "Function",
- "Instance",
- "Session",
- "Item",
- "Class",
- "File",
- "_fillfuncargs",
- )
- def _setup_collect_fakemodule():
- from types import ModuleType
- import pytest
- pytest.collect = ModuleType("pytest.collect")
- pytest.collect.__all__ = [] # used for setns
- for attribute in COLLECT_FAKEMODULE_ATTRIBUTES:
- setattr(pytest.collect, attribute, getattr(pytest, attribute))
- if _PY2:
- # Without this the test_dupfile_on_textio will fail, otherwise CaptureIO could directly inherit from StringIO.
- from py.io import TextIO
- class CaptureIO(TextIO):
- @property
- def encoding(self):
- return getattr(self, "_encoding", "UTF-8")
- else:
- import io
- class CaptureIO(io.TextIOWrapper):
- def __init__(self):
- super(CaptureIO, self).__init__(
- io.BytesIO(), encoding="UTF-8", newline="", write_through=True
- )
- def getvalue(self):
- return self.buffer.getvalue().decode("UTF-8")
- class FuncargnamesCompatAttr(object):
- """ helper class so that Metafunc, Function and FixtureRequest
- don't need to each define the "funcargnames" compatibility attribute.
- """
- @property
- def funcargnames(self):
- """ alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
- return self.fixturenames
- if six.PY2:
- def lru_cache(*_, **__):
- def dec(fn):
- return fn
- return dec
- else:
- from functools import lru_cache # noqa: F401
- if getattr(attr, "__version_info__", ()) >= (19, 2):
- ATTRS_EQ_FIELD = "eq"
- else:
- ATTRS_EQ_FIELD = "cmp"
|