old.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. # -*- coding: utf-8 -*-
  2. # <sure - utility belt for automated testing in python>
  3. # Copyright (C) <2010-2017> Gabriel Falcão <gabriel@nacaolivre.org>
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. from __future__ import unicode_literals
  18. import os
  19. import re
  20. import traceback
  21. import inspect
  22. from copy import deepcopy
  23. from pprint import pformat
  24. from functools import wraps
  25. try:
  26. from collections import Iterable
  27. except ImportError:
  28. Iterable = (list, dict, tuple, set)
  29. try:
  30. import __builtin__ as builtins
  31. except ImportError:
  32. import builtins
  33. try:
  34. from re import Pattern
  35. except ImportError:
  36. Pattern = re._pattern_type
  37. from six import string_types, text_type
  38. from sure.core import DeepComparison
  39. from sure.core import _get_file_name
  40. from sure.core import _get_line_number
  41. from sure.core import itemize_length
  42. def identify_callable_location(callable_object):
  43. filename = os.path.relpath(callable_object.__code__.co_filename)
  44. lineno = callable_object.__code__.co_firstlineno
  45. callable_name = callable_object.__code__.co_name
  46. return '{0} [{1} line {2}]'.format(callable_name, filename, lineno).encode()
  47. def is_iterable(obj):
  48. return hasattr(obj, '__iter__') and not isinstance(obj, string_types)
  49. def all_integers(obj):
  50. if not is_iterable(obj):
  51. return
  52. for element in obj:
  53. if not isinstance(element, int):
  54. return
  55. return True
  56. def explanation(msg):
  57. def dec(func):
  58. @wraps(func)
  59. def wrap(self, what):
  60. ret = func(self, what)
  61. assert ret, msg % (self._src, what)
  62. return True
  63. return wrap
  64. return dec
  65. class AssertionHelper(object):
  66. def __init__(self, src,
  67. within_range=None,
  68. with_args=None,
  69. with_kwargs=None,
  70. and_kwargs=None):
  71. self._src = src
  72. self._attribute = None
  73. self._eval = None
  74. self._range = None
  75. if all_integers(within_range):
  76. if len(within_range) != 2:
  77. raise TypeError(
  78. 'within_range parameter must be a tuple with 2 objects',
  79. )
  80. self._range = within_range
  81. self._callable_args = []
  82. if isinstance(with_args, (list, tuple)):
  83. self._callable_args = list(with_args)
  84. self._callable_kw = {}
  85. if isinstance(with_kwargs, dict):
  86. self._callable_kw.update(with_kwargs)
  87. if isinstance(and_kwargs, dict):
  88. self._callable_kw.update(and_kwargs)
  89. @classmethod
  90. def is_a_matcher(cls, func):
  91. def match(self, *args, **kw):
  92. return func(self._src, *args, **kw)
  93. new_matcher = deepcopy(match)
  94. new_matcher.__name__ = func.__name__
  95. setattr(cls, func.__name__, new_matcher)
  96. return new_matcher
  97. def raises(self, exc, msg=None):
  98. if not callable(self._src):
  99. raise TypeError('%r is not callable' % self._src)
  100. try:
  101. self._src(*self._callable_args, **self._callable_kw)
  102. except BaseException as e:
  103. if isinstance(exc, string_types):
  104. msg = exc
  105. exc = type(e)
  106. elif isinstance(exc, Pattern):
  107. msg = exc
  108. exc = type(e)
  109. err = text_type(e)
  110. if isinstance(exc, type) and issubclass(exc, BaseException):
  111. if not isinstance(e, exc):
  112. raise AssertionError(
  113. '%r should raise %r, but raised %r:\nORIGINAL EXCEPTION:\n\n%s' % (
  114. self._src, exc, e.__class__, traceback.format_exc()))
  115. if isinstance(msg, string_types) and msg not in err:
  116. raise AssertionError('''
  117. %r raised %s, but the exception message does not
  118. match.\n\nEXPECTED:\n%s\n\nGOT:\n%s'''.strip() % (
  119. self._src,
  120. type(e).__name__,
  121. msg, err))
  122. elif isinstance(msg, Pattern) and not msg.search(err):
  123. raise AssertionError(
  124. 'When calling %r the exception message does not match. ' \
  125. 'Expected to match regex: %r\n against:\n %r' % (identify_callable_location(self._src), msg.pattern, err))
  126. elif isinstance(msg, string_types) and msg not in err:
  127. raise AssertionError(
  128. 'When calling %r the exception message does not match. ' \
  129. 'Expected: %r\n got:\n %r' % (self._src, msg, err))
  130. elif isinstance(msg, Pattern) and not msg.search(err):
  131. raise AssertionError(
  132. 'When calling %r the exception message does not match. ' \
  133. 'Expected to match regex: %r\n against:\n %r' % (identify_callable_location(self._src), msg.pattern, err))
  134. else:
  135. raise e
  136. else:
  137. if inspect.isbuiltin(self._src):
  138. _src_filename = '<built-in function>'
  139. else:
  140. _src_filename = _get_file_name(self._src)
  141. if inspect.isfunction(self._src):
  142. _src_lineno = _get_line_number(self._src)
  143. raise AssertionError(
  144. 'calling function %s(%s at line: "%d") with args %r and kwargs %r did not raise %r' % (
  145. self._src.__name__,
  146. _src_filename, _src_lineno,
  147. self._callable_args,
  148. self._callable_kw, exc))
  149. else:
  150. raise AssertionError(
  151. 'at %s:\ncalling %s() with args %r and kwargs %r did not raise %r' % (
  152. _src_filename,
  153. self._src.__name__,
  154. self._callable_args,
  155. self._callable_kw, exc))
  156. return True
  157. def deep_equals(self, dst):
  158. deep = DeepComparison(self._src, dst)
  159. comparison = deep.compare()
  160. if isinstance(comparison, bool):
  161. return comparison
  162. raise comparison.as_assertion(self._src, dst)
  163. def equals(self, dst):
  164. if self._attribute and is_iterable(self._src):
  165. msg = '%r[%d].%s should be %r, but is %r'
  166. for index, item in enumerate(self._src):
  167. if self._range:
  168. if index < self._range[0] or index > self._range[1]:
  169. continue
  170. attribute = getattr(item, self._attribute)
  171. error = msg % (
  172. self._src, index, self._attribute, dst, attribute)
  173. if attribute != dst:
  174. raise AssertionError(error)
  175. else:
  176. return self.deep_equals(dst)
  177. return True
  178. def looks_like(self, dst):
  179. old_src = pformat(self._src)
  180. old_dst = pformat(dst)
  181. self._src = re.sub(r'\s', '', self._src).lower()
  182. dst = re.sub(r'\s', '', dst).lower()
  183. error = '%s does not look like %s' % (old_src, old_dst)
  184. assert self._src == dst, error
  185. return self._src == dst
  186. def every_one_is(self, dst):
  187. msg = 'all members of %r should be %r, but the %dth is %r'
  188. for index, item in enumerate(self._src):
  189. if self._range:
  190. if index < self._range[0] or index > self._range[1]:
  191. continue
  192. error = msg % (self._src, dst, index, item)
  193. if item != dst:
  194. raise AssertionError(error)
  195. return True
  196. @explanation('%r should differ from %r, but is the same thing')
  197. def differs(self, dst):
  198. return self._src != dst
  199. @explanation('%r should be a instance of %r, but is not')
  200. def is_a(self, dst):
  201. return isinstance(self._src, dst)
  202. def at(self, key):
  203. assert self.has(key)
  204. if isinstance(self._src, dict):
  205. return AssertionHelper(self._src[key])
  206. else:
  207. return AssertionHelper(getattr(self._src, key))
  208. @explanation('%r should have %r, but have not')
  209. def has(self, that):
  210. return that in self
  211. def _get_that(self, that):
  212. try:
  213. that = int(that)
  214. except TypeError:
  215. that = len(that)
  216. return that
  217. def len_greater_than(self, that):
  218. that = self._get_that(that)
  219. length = len(self._src)
  220. if length <= that:
  221. error = 'the length of the %s should be greater then %d, but is %d' % (
  222. type(self._src).__name__,
  223. that,
  224. length,
  225. )
  226. raise AssertionError(error)
  227. return True
  228. def len_greater_than_or_equals(self, that):
  229. that = self._get_that(that)
  230. length = len(self._src)
  231. if length < that:
  232. error = 'the length of %r should be greater then or equals %d, but is %d' % (
  233. self._src,
  234. that,
  235. length,
  236. )
  237. raise AssertionError(error)
  238. return True
  239. def len_lower_than(self, that):
  240. original_that = that
  241. if isinstance(that, Iterable):
  242. that = len(that)
  243. else:
  244. that = self._get_that(that)
  245. length = len(self._src)
  246. if length >= that:
  247. error = 'the length of %r should be lower then %r, but is %d' % (
  248. self._src,
  249. original_that,
  250. length,
  251. )
  252. raise AssertionError(error)
  253. return True
  254. def len_lower_than_or_equals(self, that):
  255. that = self._get_that(that)
  256. length = len(self._src)
  257. error = 'the length of %r should be lower then or equals %d, but is %d'
  258. if length > that:
  259. msg = error % (
  260. self._src,
  261. that,
  262. length,
  263. )
  264. raise AssertionError(msg)
  265. return True
  266. def len_is(self, that):
  267. that = self._get_that(that)
  268. length = len(self._src)
  269. if length != that:
  270. error = 'the length of %r should be %d, but is %d' % (
  271. self._src,
  272. that,
  273. length,
  274. )
  275. raise AssertionError(error)
  276. return True
  277. def len_is_not(self, that):
  278. that = self._get_that(that)
  279. length = len(self._src)
  280. if length == that:
  281. error = 'the length of %r should not be %d' % (
  282. self._src,
  283. that,
  284. )
  285. raise AssertionError(error)
  286. return True
  287. def like(self, that):
  288. return self.has(that)
  289. def the_attribute(self, attr):
  290. self._attribute = attr
  291. return self
  292. def in_each(self, attr):
  293. self._eval = attr
  294. return self
  295. def matches(self, items):
  296. msg = '%r[%d].%s should be %r, but is %r'
  297. get_eval = lambda item: eval(
  298. "%s.%s" % ('current', self._eval), {}, {'current': item},
  299. )
  300. if self._eval and is_iterable(self._src):
  301. if isinstance(items, string_types):
  302. items = [items for x in range(len(items))]
  303. else:
  304. if len(items) != len(self._src):
  305. source = list(map(get_eval, self._src))
  306. source_len = len(source)
  307. items_len = len(items)
  308. raise AssertionError(
  309. '%r has %d items, but the matching list has %d: %r'
  310. % (source, source_len, items_len, items),
  311. )
  312. for index, (item, other) in enumerate(zip(self._src, items)):
  313. if self._range:
  314. if index < self._range[0] or index > self._range[1]:
  315. continue
  316. value = get_eval(item)
  317. error = msg % (self._src, index, self._eval, other, value)
  318. if other != value:
  319. raise AssertionError(error)
  320. else:
  321. return self.equals(items)
  322. return True
  323. @builtins.property
  324. def is_empty(self):
  325. try:
  326. lst = list(self._src)
  327. length = len(lst)
  328. assert length == 0, \
  329. '%r is not empty, it has %s' % (self._src,
  330. itemize_length(self._src))
  331. return True
  332. except TypeError:
  333. raise AssertionError("%r is not iterable" % self._src)
  334. @builtins.property
  335. def are_empty(self):
  336. return self.is_empty
  337. def __contains__(self, what):
  338. if isinstance(self._src, dict):
  339. items = self._src.keys()
  340. if isinstance(self._src, Iterable):
  341. items = self._src
  342. else:
  343. items = dir(self._src)
  344. return what in items
  345. def contains(self, what):
  346. assert what in self._src, '%r should be in %r' % (what, self._src)
  347. return True
  348. def does_not_contain(self, what):
  349. assert what not in self._src, \
  350. '%r should NOT be in %r' % (what, self._src)
  351. return True
  352. doesnt_contain = does_not_contain
  353. that = AssertionHelper