compat.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. """
  2. python version compatibility code
  3. """
  4. from __future__ import absolute_import, division, print_function
  5. import codecs
  6. import functools
  7. import inspect
  8. import re
  9. import sys
  10. from contextlib import contextmanager
  11. import py
  12. import _pytest
  13. from _pytest.outcomes import TEST_OUTCOME
  14. from six import text_type
  15. import six
  16. try:
  17. import enum
  18. except ImportError: # pragma: no cover
  19. # Only available in Python 3.4+ or as a backport
  20. enum = None
  21. __all__ = ["Path"]
  22. _PY3 = sys.version_info > (3, 0)
  23. _PY2 = not _PY3
  24. if _PY3:
  25. from inspect import signature, Parameter as Parameter
  26. else:
  27. from funcsigs import signature, Parameter as Parameter
  28. NoneType = type(None)
  29. NOTSET = object()
  30. PY35 = sys.version_info[:2] >= (3, 5)
  31. PY36 = sys.version_info[:2] >= (3, 6)
  32. MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
  33. if PY36:
  34. from pathlib import Path
  35. else:
  36. from pathlib2 import Path
  37. if _PY3:
  38. from collections.abc import MutableMapping as MappingMixin
  39. from collections.abc import Mapping, Sequence
  40. else:
  41. # those raise DeprecationWarnings in Python >=3.7
  42. from collections import MutableMapping as MappingMixin # noqa
  43. from collections import Mapping, Sequence # noqa
  44. def _format_args(func):
  45. return str(signature(func))
  46. isfunction = inspect.isfunction
  47. isclass = inspect.isclass
  48. # used to work around a python2 exception info leak
  49. exc_clear = getattr(sys, "exc_clear", lambda: None)
  50. # The type of re.compile objects is not exposed in Python.
  51. REGEX_TYPE = type(re.compile(""))
  52. def is_generator(func):
  53. genfunc = inspect.isgeneratorfunction(func)
  54. return genfunc and not iscoroutinefunction(func)
  55. def iscoroutinefunction(func):
  56. """Return True if func is a decorated coroutine function.
  57. Note: copied and modified from Python 3.5's builtin couroutines.py to avoid import asyncio directly,
  58. which in turns also initializes the "logging" module as side-effect (see issue #8).
  59. """
  60. return getattr(func, "_is_coroutine", False) or (
  61. hasattr(inspect, "iscoroutinefunction") and inspect.iscoroutinefunction(func)
  62. )
  63. def getlocation(function, curdir):
  64. function = get_real_func(function)
  65. fn = py.path.local(inspect.getfile(function))
  66. lineno = function.__code__.co_firstlineno
  67. if fn.relto(curdir):
  68. fn = fn.relto(curdir)
  69. return "%s:%d" % (fn, lineno + 1)
  70. def num_mock_patch_args(function):
  71. """ return number of arguments used up by mock arguments (if any) """
  72. patchings = getattr(function, "patchings", None)
  73. if not patchings:
  74. return 0
  75. mock_modules = [sys.modules.get("mock"), sys.modules.get("unittest.mock")]
  76. if any(mock_modules):
  77. sentinels = [m.DEFAULT for m in mock_modules if m is not None]
  78. return len(
  79. [p for p in patchings if not p.attribute_name and p.new in sentinels]
  80. )
  81. return len(patchings)
  82. def getfuncargnames(function, is_method=False, cls=None):
  83. """Returns the names of a function's mandatory arguments.
  84. This should return the names of all function arguments that:
  85. * Aren't bound to an instance or type as in instance or class methods.
  86. * Don't have default values.
  87. * Aren't bound with functools.partial.
  88. * Aren't replaced with mocks.
  89. The is_method and cls arguments indicate that the function should
  90. be treated as a bound method even though it's not unless, only in
  91. the case of cls, the function is a static method.
  92. @RonnyPfannschmidt: This function should be refactored when we
  93. revisit fixtures. The fixture mechanism should ask the node for
  94. the fixture names, and not try to obtain directly from the
  95. function object well after collection has occurred.
  96. """
  97. # The parameters attribute of a Signature object contains an
  98. # ordered mapping of parameter names to Parameter instances. This
  99. # creates a tuple of the names of the parameters that don't have
  100. # defaults.
  101. arg_names = tuple(
  102. p.name
  103. for p in signature(function).parameters.values()
  104. if (
  105. p.kind is Parameter.POSITIONAL_OR_KEYWORD
  106. or p.kind is Parameter.KEYWORD_ONLY
  107. )
  108. and p.default is Parameter.empty
  109. )
  110. # If this function should be treated as a bound method even though
  111. # it's passed as an unbound method or function, remove the first
  112. # parameter name.
  113. if is_method or (
  114. cls and not isinstance(cls.__dict__.get(function.__name__, None), staticmethod)
  115. ):
  116. arg_names = arg_names[1:]
  117. # Remove any names that will be replaced with mocks.
  118. if hasattr(function, "__wrapped__"):
  119. arg_names = arg_names[num_mock_patch_args(function) :]
  120. return arg_names
  121. @contextmanager
  122. def dummy_context_manager():
  123. """Context manager that does nothing, useful in situations where you might need an actual context manager or not
  124. depending on some condition. Using this allow to keep the same code"""
  125. yield
  126. def get_default_arg_names(function):
  127. # Note: this code intentionally mirrors the code at the beginning of getfuncargnames,
  128. # to get the arguments which were excluded from its result because they had default values
  129. return tuple(
  130. p.name
  131. for p in signature(function).parameters.values()
  132. if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY)
  133. and p.default is not Parameter.empty
  134. )
  135. if _PY3:
  136. STRING_TYPES = bytes, str
  137. UNICODE_TYPES = six.text_type
  138. if PY35:
  139. def _bytes_to_ascii(val):
  140. return val.decode("ascii", "backslashreplace")
  141. else:
  142. def _bytes_to_ascii(val):
  143. if val:
  144. # source: http://goo.gl/bGsnwC
  145. encoded_bytes, _ = codecs.escape_encode(val)
  146. return encoded_bytes.decode("ascii")
  147. else:
  148. # empty bytes crashes codecs.escape_encode (#1087)
  149. return ""
  150. def ascii_escaped(val):
  151. """If val is pure ascii, returns it as a str(). Otherwise, escapes
  152. bytes objects into a sequence of escaped bytes:
  153. b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6'
  154. and escapes unicode objects into a sequence of escaped unicode
  155. ids, e.g.:
  156. '4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944'
  157. note:
  158. the obvious "v.decode('unicode-escape')" will return
  159. valid utf-8 unicode if it finds them in bytes, but we
  160. want to return escaped bytes for any byte, even if they match
  161. a utf-8 string.
  162. """
  163. if isinstance(val, bytes):
  164. return _bytes_to_ascii(val)
  165. else:
  166. return val.encode("unicode_escape").decode("ascii")
  167. else:
  168. STRING_TYPES = six.string_types
  169. UNICODE_TYPES = six.text_type
  170. def ascii_escaped(val):
  171. """In py2 bytes and str are the same type, so return if it's a bytes
  172. object, return it unchanged if it is a full ascii string,
  173. otherwise escape it into its binary form.
  174. If it's a unicode string, change the unicode characters into
  175. unicode escapes.
  176. """
  177. if isinstance(val, bytes):
  178. try:
  179. return val.encode("ascii")
  180. except UnicodeDecodeError:
  181. return val.encode("string-escape")
  182. else:
  183. return val.encode("unicode-escape")
  184. class _PytestWrapper(object):
  185. """Dummy wrapper around a function object for internal use only.
  186. Used to correctly unwrap the underlying function object
  187. when we are creating fixtures, because we wrap the function object ourselves with a decorator
  188. to issue warnings when the fixture function is called directly.
  189. """
  190. def __init__(self, obj):
  191. self.obj = obj
  192. def get_real_func(obj):
  193. """ gets the real function object of the (possibly) wrapped object by
  194. functools.wraps or functools.partial.
  195. """
  196. start_obj = obj
  197. for i in range(100):
  198. # __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function
  199. # to trigger a warning if it gets called directly instead of by pytest: we don't
  200. # want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774)
  201. new_obj = getattr(obj, "__pytest_wrapped__", None)
  202. if isinstance(new_obj, _PytestWrapper):
  203. obj = new_obj.obj
  204. break
  205. new_obj = getattr(obj, "__wrapped__", None)
  206. if new_obj is None:
  207. break
  208. obj = new_obj
  209. else:
  210. raise ValueError(
  211. ("could not find real function of {start}" "\nstopped at {current}").format(
  212. start=py.io.saferepr(start_obj), current=py.io.saferepr(obj)
  213. )
  214. )
  215. if isinstance(obj, functools.partial):
  216. obj = obj.func
  217. return obj
  218. def get_real_method(obj, holder):
  219. """
  220. Attempts to obtain the real function object that might be wrapping ``obj``, while at the same time
  221. returning a bound method to ``holder`` if the original object was a bound method.
  222. """
  223. try:
  224. is_method = hasattr(obj, "__func__")
  225. obj = get_real_func(obj)
  226. except Exception:
  227. return obj
  228. if is_method and hasattr(obj, "__get__") and callable(obj.__get__):
  229. obj = obj.__get__(holder)
  230. return obj
  231. def getfslineno(obj):
  232. # xxx let decorators etc specify a sane ordering
  233. obj = get_real_func(obj)
  234. if hasattr(obj, "place_as"):
  235. obj = obj.place_as
  236. fslineno = _pytest._code.getfslineno(obj)
  237. assert isinstance(fslineno[1], int), obj
  238. return fslineno
  239. def getimfunc(func):
  240. try:
  241. return func.__func__
  242. except AttributeError:
  243. return func
  244. def safe_getattr(object, name, default):
  245. """ Like getattr but return default upon any Exception or any OutcomeException.
  246. Attribute access can potentially fail for 'evil' Python objects.
  247. See issue #214.
  248. It catches OutcomeException because of #2490 (issue #580), new outcomes are derived from BaseException
  249. instead of Exception (for more details check #2707)
  250. """
  251. try:
  252. return getattr(object, name, default)
  253. except TEST_OUTCOME:
  254. return default
  255. def _is_unittest_unexpected_success_a_failure():
  256. """Return if the test suite should fail if an @expectedFailure unittest test PASSES.
  257. From https://docs.python.org/3/library/unittest.html?highlight=unittest#unittest.TestResult.wasSuccessful:
  258. Changed in version 3.4: Returns False if there were any
  259. unexpectedSuccesses from tests marked with the expectedFailure() decorator.
  260. """
  261. return sys.version_info >= (3, 4)
  262. if _PY3:
  263. def safe_str(v):
  264. """returns v as string"""
  265. return str(v)
  266. else:
  267. def safe_str(v):
  268. """returns v as string, converting to ascii if necessary"""
  269. try:
  270. return str(v)
  271. except UnicodeError:
  272. if not isinstance(v, text_type):
  273. v = text_type(v)
  274. errors = "replace"
  275. return v.encode("utf-8", errors)
  276. COLLECT_FAKEMODULE_ATTRIBUTES = (
  277. "Collector",
  278. "Module",
  279. "Generator",
  280. "Function",
  281. "Instance",
  282. "Session",
  283. "Item",
  284. "Class",
  285. "File",
  286. "_fillfuncargs",
  287. )
  288. def _setup_collect_fakemodule():
  289. from types import ModuleType
  290. import pytest
  291. pytest.collect = ModuleType("pytest.collect")
  292. pytest.collect.__all__ = [] # used for setns
  293. for attr in COLLECT_FAKEMODULE_ATTRIBUTES:
  294. setattr(pytest.collect, attr, getattr(pytest, attr))
  295. if _PY2:
  296. # Without this the test_dupfile_on_textio will fail, otherwise CaptureIO could directly inherit from StringIO.
  297. from py.io import TextIO
  298. class CaptureIO(TextIO):
  299. @property
  300. def encoding(self):
  301. return getattr(self, "_encoding", "UTF-8")
  302. else:
  303. import io
  304. class CaptureIO(io.TextIOWrapper):
  305. def __init__(self):
  306. super(CaptureIO, self).__init__(
  307. io.BytesIO(), encoding="UTF-8", newline="", write_through=True
  308. )
  309. def getvalue(self):
  310. return self.buffer.getvalue().decode("UTF-8")
  311. class FuncargnamesCompatAttr(object):
  312. """ helper class so that Metafunc, Function and FixtureRequest
  313. don't need to each define the "funcargnames" compatibility attribute.
  314. """
  315. @property
  316. def funcargnames(self):
  317. """ alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
  318. return self.fixturenames