__init__.py 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091
  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 re
  19. import os
  20. import sys
  21. import difflib
  22. import inspect
  23. import traceback
  24. from functools import wraps, partial
  25. from datetime import datetime
  26. from six import string_types, text_type, PY2, get_function_code
  27. from six.moves import reduce
  28. from sure.old import AssertionHelper
  29. from sure.old import Iterable
  30. from sure.old import builtins
  31. from sure.core import DeepComparison
  32. from sure.core import DeepExplanation
  33. from sure.core import _get_file_name
  34. from sure.core import _get_line_number
  35. from sure.core import safe_repr
  36. from sure.core import anything # noqa
  37. from sure.magic import is_cpython, patchable_builtin
  38. from sure.registry import context as _registry
  39. if not PY2:
  40. basestring = str
  41. version = '1.4.11'
  42. not_here_error = \
  43. 'you have tried to access the attribute %r from the context ' \
  44. '(aka VariablesBag), but there is no such attribute assigned to it. ' \
  45. 'Maybe you misspelled it ? Well, here are the options: %s'
  46. original_obj_attrs = dir(object)
  47. class VariablesBag(dict):
  48. __varnames__ = None
  49. __sure_actions_ran__ = None
  50. __sure_action_results__ = None
  51. __sure_providers_of__ = None
  52. def __init__(self, *args, **kw):
  53. self.__varnames__ = []
  54. self.__sure_actions_ran__ = []
  55. self.__sure_action_results__ = []
  56. self.__sure_providers_of__ = {}
  57. return super(VariablesBag, self).__init__(*args, **kw)
  58. def __setattr__(self, attr, value):
  59. if attr not in dir(VariablesBag):
  60. self[attr] = value
  61. self.__varnames__.append(attr)
  62. return super(VariablesBag, self).__setattr__(attr, value)
  63. def __getattr__(self, attr):
  64. try:
  65. return super(VariablesBag, self).__getattribute__(attr)
  66. except AttributeError:
  67. if attr not in dir(VariablesBag):
  68. raise AssertionError(not_here_error % (
  69. attr,
  70. safe_repr(self.__varnames__),
  71. ))
  72. def ensure_type(caller_name, cast, obj):
  73. try:
  74. return cast(obj)
  75. except TypeError:
  76. raise AssertionError('{0} tried to ')
  77. class CallBack(object):
  78. context_error = "the function %s defined at %s line %d, is being "\
  79. "decorated by either @that_with_context or @scenario, so it should " \
  80. "take at least 1 parameter, which is the test context"
  81. def __init__(self, cb, args, kwargs):
  82. self.callback = cb
  83. self.args = args or []
  84. self.kwargs = kwargs or {}
  85. self.callback_name = cb.__name__
  86. self.callback_filename = os.path.split(get_function_code(cb).co_filename)[-1]
  87. self.callback_lineno = get_function_code(cb).co_firstlineno + 1
  88. def apply(self, *optional_args):
  89. args = list(optional_args)
  90. args.extend(self.args)
  91. try:
  92. return self.callback(*args, **self.kwargs)
  93. except Exception:
  94. exc_klass, exc_value, tb = sys.exc_info()
  95. err = traceback.format_exc().splitlines()[-1]
  96. err = err.replace('{0}:'.format(exc_klass.__name__), '').strip()
  97. if err.startswith(self.callback_name) and \
  98. ('takes no arguments (1 given)' in err or
  99. 'takes 0 positional arguments but 1 was given' in err):
  100. raise TypeError(self.context_error % (
  101. self.callback_name,
  102. self.callback_filename,
  103. self.callback_lineno,
  104. )
  105. )
  106. raise
  107. def that_with_context(setup=None, teardown=None):
  108. def dec(func):
  109. @wraps(func)
  110. def wrap(*args, **kw):
  111. context = VariablesBag()
  112. if callable(setup):
  113. cb = CallBack(setup, args, kw)
  114. cb.apply(context)
  115. elif isinstance(setup, Iterable):
  116. for s in setup:
  117. cb = CallBack(s, args, kw)
  118. cb.apply(context)
  119. test = CallBack(func, args, kw)
  120. try:
  121. res = test.apply(context)
  122. finally:
  123. if callable(teardown):
  124. cb = CallBack(teardown, args, kw)
  125. cb.apply(context)
  126. elif isinstance(teardown, Iterable):
  127. for s in teardown:
  128. cb = CallBack(s, args, kw)
  129. cb.apply(context)
  130. return res
  131. return wrap
  132. return dec
  133. scenario = that_with_context
  134. def within(**units):
  135. assert len(units) == 1, 'use within(number=unit). e.g.: within(one=second)'
  136. word, unit = list(units.items())[0]
  137. value = word_to_number(word)
  138. convert_from, convert_to = UNITS[unit]
  139. timeout = convert_from(value)
  140. exc = []
  141. def dec(func):
  142. def wrap(*args, **kw):
  143. start = datetime.utcnow()
  144. try:
  145. func(start, *args, **kw)
  146. except TypeError as e:
  147. if PY2:
  148. # PY2 has different error message
  149. fmt = '{0}() takes no arguments'
  150. else:
  151. fmt = '{0}() takes 0 positional arguments but 1 was given'
  152. err = text_type(e)
  153. if fmt.format(func.__name__) in err:
  154. func(*args, **kw)
  155. else:
  156. exc.append(traceback.format_exc())
  157. except Exception as e:
  158. exc.append(traceback.format_exc())
  159. end = datetime.utcnow()
  160. delta = (end - start)
  161. took = convert_to(delta.microseconds)
  162. print(took, timeout)
  163. assert took < timeout, \
  164. '%s did not run within %s %s' % (func.__name__, word, unit)
  165. if exc:
  166. raise AssertionError(exc.pop(0))
  167. wrap.__name__ = func.__name__
  168. wrap.__doc__ = func.__doc__
  169. wrap.__dict__ = func.__dict__
  170. return wrap
  171. return dec
  172. UNITS = {
  173. 'minutes': (
  174. lambda from_num: from_num / 60.0,
  175. lambda to_num: to_num * 6000000,
  176. ),
  177. 'seconds': (
  178. lambda from_num: from_num,
  179. lambda to_num: to_num / 100000,
  180. ),
  181. 'miliseconds': (
  182. lambda from_num: from_num * 1000,
  183. lambda to_num: to_num / 100,
  184. ),
  185. 'microseconds': (
  186. lambda from_num: from_num * 100000,
  187. lambda to_num: to_num,
  188. ),
  189. }
  190. milisecond = miliseconds = 'miliseconds'
  191. microsecond = microseconds = 'microseconds'
  192. second = seconds = 'seconds'
  193. minute = minutes = 'minutes'
  194. def word_to_number(word):
  195. basic = {
  196. 'one': 1,
  197. 'two': 2,
  198. 'three': 3,
  199. 'four': 4,
  200. 'five': 5,
  201. 'six': 6,
  202. 'seven': 7,
  203. 'eight': 8,
  204. 'nine': 9,
  205. 'ten': 10,
  206. 'eleven': 11,
  207. 'twelve': 12,
  208. }
  209. try:
  210. return basic[word]
  211. except KeyError:
  212. raise AssertionError(
  213. 'sure supports only literal numbers from one to twelve, ' \
  214. 'you tried the word "twenty"')
  215. def action_for(context, provides=None, depends_on=None):
  216. if not provides:
  217. provides = []
  218. if not depends_on:
  219. depends_on = []
  220. def register_providers(func, attr):
  221. if re.search(r'^[{]\d+[}]$', attr):
  222. return # ignore dinamically declared provides
  223. if not attr in context.__sure_providers_of__:
  224. context.__sure_providers_of__[attr] = []
  225. context.__sure_providers_of__[attr].append(func)
  226. def register_dinamic_providers(func, attr, args, kwargs):
  227. found = re.search(r'^[{](\d+)[}]$', attr)
  228. if not found:
  229. return # ignore dinamically declared provides
  230. index = int(found.group(1))
  231. assert index < len(args), \
  232. 'the dinamic provider index: {%d} is bigger than %d, which is ' \
  233. 'the length of the positional arguments passed to %s' % (
  234. index, len(args), func.__name__)
  235. attr = args[index]
  236. if not attr in context.__sure_providers_of__:
  237. context.__sure_providers_of__[attr] = []
  238. context.__sure_providers_of__[attr].append(func)
  239. def ensure_providers(func, attr, args, kwargs):
  240. found = re.search(r'^[{](\d+)[}]$', attr)
  241. if found:
  242. index = int(found.group(1))
  243. attr = args[index]
  244. assert attr in context, \
  245. 'the action "%s" was supposed to provide the attribute "%s" ' \
  246. 'into the context, but it did not. Please double check its ' \
  247. 'implementation' % (func.__name__, attr)
  248. dependency_error_lonely = 'the action "%s" defined at %s:%d ' \
  249. 'depends on the attribute "%s" to be available in the' \
  250. ' context. It turns out that there are no actions providing ' \
  251. 'that. Please double-check the implementation'
  252. dependency_error_hints = 'the action "%s" defined at %s:%d ' \
  253. 'depends on the attribute "%s" to be available in the context.'\
  254. ' You need to call one of the following actions beforehand:\n'
  255. def check_dependencies(func):
  256. action = func.__name__
  257. filename = _get_file_name(func)
  258. lineno = _get_line_number(func)
  259. for dependency in depends_on:
  260. if dependency in context.__sure_providers_of__:
  261. providers = context.__sure_providers_of__[dependency]
  262. err = dependency_error_hints % (
  263. action,
  264. filename,
  265. lineno,
  266. dependency,
  267. )
  268. err += '\n'.join([
  269. ' -> %s at %s:%d' % (
  270. p.__name__,
  271. _get_file_name(p),
  272. _get_line_number(p)) for p in providers])
  273. else:
  274. err = dependency_error_lonely % (
  275. action,
  276. filename,
  277. lineno,
  278. dependency,
  279. )
  280. assert dependency in context, err
  281. def decorate_and_absorb(func):
  282. [register_providers(func, attr) for attr in provides]
  283. @wraps(func)
  284. def wrapper(*args, **kw):
  285. [register_dinamic_providers(func, attr, args, kw)
  286. for attr in provides]
  287. context.__sure_actions_ran__.append((func, args, kw))
  288. check_dependencies(func)
  289. result = func(*args, **kw)
  290. [ensure_providers(func, attr, args, kw) for attr in provides]
  291. context.__sure_action_results__.append(result)
  292. return context
  293. setattr(context, func.__name__, wrapper)
  294. return wrapper
  295. return decorate_and_absorb
  296. def work_in_progress(func):
  297. @wraps(func)
  298. def wrapper(*args, **kwargs):
  299. _registry['is_running'] = True
  300. ret = func(*args, **kwargs)
  301. _registry['is_running'] = False
  302. return ret
  303. return wrapper
  304. def assertionmethod(func):
  305. @wraps(func)
  306. def wrapper(self, *args, **kw):
  307. try:
  308. value = func(self, *args, **kw)
  309. except AssertionError as e:
  310. raise AssertionError(e)
  311. msg = "{0}({1}) failed".format(
  312. func.__name__,
  313. ", ".join(map(safe_repr, args)),
  314. ", ".join(["{0}={1}".format(k, safe_repr(kw[k])) for k in kw]),
  315. )
  316. if PY2:
  317. msg = text_type(msg)
  318. assert value, msg
  319. return value
  320. return wrapper
  321. def assertionproperty(func):
  322. return builtins.property(assertionmethod(func))
  323. POSITIVES = [
  324. 'should',
  325. 'does',
  326. 'do',
  327. 'must',
  328. 'when',
  329. ]
  330. NEGATIVES = [
  331. 'shouldnt',
  332. 'dont',
  333. 'do_not',
  334. 'doesnt',
  335. 'does_not',
  336. 'doesnot',
  337. 'should_not',
  338. 'shouldnot',
  339. ]
  340. class IdentityAssertion(object):
  341. def __init__(self, assertion_builder):
  342. self._ab = assertion_builder
  343. def __call__(self, other):
  344. if self._ab.negative:
  345. assert self._ab.obj is not other, "{0} should not be the same object as {1}, but it is".format(self._ab.obj, other)
  346. return True
  347. assert self._ab.obj is other, "{0} should be the same object as {1}, but it is not".format(self._ab.obj, other)
  348. return True
  349. def __getattr__(self, name):
  350. return getattr(self._ab, name)
  351. class AssertionBuilder(object):
  352. def __init__(self, name=None, negative=False, obj=None, callable_args=None, callable_kw=None):
  353. self._name = name
  354. self.negative = negative
  355. self.obj = obj
  356. self._callable_args = callable_args or []
  357. self._callable_kw = callable_kw or {}
  358. self._that = AssertionHelper(self.obj)
  359. def __call__(self, obj):
  360. self.obj = obj
  361. if isinstance(obj, self.__class__):
  362. self.obj = obj.obj
  363. self._callable_args = obj._callable_args
  364. self._callable_kw = obj._callable_kw
  365. self._that = AssertionHelper(self.obj)
  366. return self
  367. def __getattr__(self, attr):
  368. special_case = False
  369. special_case = attr in (POSITIVES + NEGATIVES)
  370. negative = attr in NEGATIVES
  371. if special_case:
  372. return AssertionBuilder(attr, negative=negative, obj=self.obj,
  373. callable_args=self._callable_args, callable_kw=self._callable_kw)
  374. return super(AssertionBuilder, self).__getattribute__(attr)
  375. @assertionproperty
  376. def callable(self):
  377. if self.negative:
  378. assert not callable(self.obj), (
  379. 'expected `{0}` to not be callable but it is'.format(safe_repr(self.obj)))
  380. else:
  381. assert callable(self.obj), (
  382. 'expected {0} to be callable'.format(safe_repr(self.obj)))
  383. return True
  384. @assertionproperty
  385. def be(self):
  386. return IdentityAssertion(self)
  387. being = be
  388. @assertionproperty
  389. def not_be(self):
  390. return IdentityAssertion(self.should_not)
  391. not_being = not_be
  392. @assertionproperty
  393. def not_have(self):
  394. return self.should_not
  395. @assertionproperty
  396. def to_not(self):
  397. return self.should_not
  398. @assertionproperty
  399. def to(self):
  400. return self
  401. which = to
  402. @assertionproperty
  403. def when(self):
  404. return self
  405. @assertionproperty
  406. def have(self):
  407. return self
  408. @assertionproperty
  409. def with_value(self):
  410. return self
  411. def property(self, name):
  412. has_it = hasattr(self.obj, name)
  413. if self.negative:
  414. assert not has_it, (
  415. '%r should not have the property `%s`, '
  416. 'but it is %r' % (self.obj, name, getattr(self.obj, name)))
  417. return True
  418. assert has_it, (
  419. "%r should have the property `%s` but does not" % (
  420. self.obj, name))
  421. return expect(getattr(self.obj, name))
  422. def key(self, name):
  423. has_it = name in self.obj
  424. if self.negative:
  425. assert not has_it, (
  426. '%r should not have the key `%s`, '
  427. 'but it is %r' % (self.obj, name, self.obj[name]))
  428. return True
  429. assert has_it, (
  430. "%r should have the key `%s` but does not" % (
  431. self.obj, name))
  432. return expect(self.obj[name])
  433. @assertionproperty
  434. def empty(self):
  435. representation = safe_repr(self.obj)
  436. length = len(self.obj)
  437. if self.negative:
  438. assert length > 0, (
  439. "expected `{0}` to not be empty".format(representation))
  440. else:
  441. assert length == 0, (
  442. "expected `{0}` to be empty but it has {1} items".format(representation, length))
  443. return True
  444. @assertionproperty
  445. def ok(self):
  446. if self.negative:
  447. msg = 'expected `{0}` to be falsy'.format(self.obj)
  448. assert not bool(self.obj), msg
  449. else:
  450. msg = 'expected `{0}` to be truthy'.format(self.obj)
  451. assert bool(self.obj), msg
  452. return True
  453. truthy = ok
  454. true = ok
  455. @assertionproperty
  456. def falsy(self):
  457. if self.negative:
  458. msg = 'expected `{0}` to be truthy'.format(self.obj)
  459. assert bool(self.obj), msg
  460. else:
  461. msg = 'expected `{0}` to be falsy'.format(self.obj)
  462. assert not bool(self.obj), msg
  463. return True
  464. false = falsy
  465. @assertionproperty
  466. def none(self):
  467. if self.negative:
  468. assert self.obj is not None, (
  469. r"expected `{0}` to not be None".format(self.obj))
  470. else:
  471. assert self.obj is None, (
  472. r"expected `{0}` to be None".format(self.obj))
  473. return True
  474. @assertionmethod
  475. def within_range(self, start, end):
  476. start = ensure_type('within_range', int, start)
  477. end = ensure_type('within_range', int, end)
  478. subject = ensure_type('within_range', int, self.obj)
  479. is_within_range = subject >= start and subject <= end
  480. if self.negative:
  481. if is_within_range:
  482. raise AssertionError('expected {0} to NOT be within {1} and {2}'.format(subject, start, end))
  483. return not is_within_range
  484. else:
  485. if not is_within_range:
  486. raise AssertionError('expected {0} to be within {1} and {2}'.format(subject, start, end))
  487. return is_within_range
  488. @assertionmethod
  489. def within(self, first, *rest):
  490. if isinstance(first, Iterable):
  491. collection_should = AssertionHelper(first)
  492. if self.negative:
  493. return collection_should.does_not_contain(self.obj)
  494. else:
  495. return collection_should.contains(self.obj)
  496. elif len(rest) == 1:
  497. return self.within_range(first, rest[0])
  498. else:
  499. if self.negative:
  500. ppath = '{0}.should_not.be.within'.format(self.obj)
  501. else:
  502. ppath = '{0}.should.be.within'.format(self.obj)
  503. raise AssertionError((
  504. '{0}({1}, {2}) must be called with either a iterable:\n'
  505. '{0}([1, 2, 3, 4])\n'
  506. 'or with a range of numbers:'
  507. '{0}(1, 3000)'
  508. ).format(ppath, first, ", ".join([repr(x) for x in rest])))
  509. @assertionmethod
  510. def equal(self, what, epsilon=None):
  511. """compares given object ``X`` with an expected ``Y`` object.
  512. It primarily assures that the compared objects are absolute equal ``==``.
  513. :param what: the expected value
  514. :param epsilon: a delta to leverage upper-bound floating point permissiveness
  515. """
  516. try:
  517. comparison = DeepComparison(self.obj, what, epsilon).compare()
  518. error = False
  519. except AssertionError as e:
  520. error = e
  521. comparison = None
  522. if isinstance(comparison, DeepExplanation):
  523. error = comparison.get_assertion(self.obj, what)
  524. if self.negative:
  525. if error:
  526. return True
  527. msg = '%s should differ from %s, but is the same thing'
  528. raise AssertionError(msg % (safe_repr(self.obj), safe_repr(what)))
  529. else:
  530. if not error:
  531. return True
  532. raise error
  533. eql = equal
  534. equals = equal
  535. equal_to = equal
  536. @assertionmethod
  537. def different_of(self, what):
  538. differ = difflib.Differ()
  539. source = self.obj.strip().splitlines(True)
  540. destination = what.strip().splitlines(True)
  541. result = differ.compare(source, destination)
  542. difference = "".join(result)
  543. if self.negative:
  544. if self.obj != what:
  545. assert not difference, "Difference:\n\n{0}".format(difference)
  546. else:
  547. if self.obj == what:
  548. raise AssertionError("{0} should be different of {1}".format(self.obj, what))
  549. return True
  550. @assertionmethod
  551. def an(self, klass):
  552. if isinstance(klass, type):
  553. class_name = klass.__name__
  554. elif isinstance(klass, string_types):
  555. class_name = klass.strip()
  556. else:
  557. class_name = text_type(klass)
  558. is_vowel = class_name[0] in 'aeiou'
  559. if isinstance(klass, string_types):
  560. if '.' in klass:
  561. items = klass.split('.')
  562. first = items.pop(0)
  563. if not items:
  564. items = [first]
  565. first = '_abcoll'
  566. else:
  567. if sys.version_info <= (3, 0, 0):
  568. first = '__builtin__'
  569. else:
  570. first = 'builtins'
  571. items = [klass]
  572. klass = reduce(getattr, items, __import__(first))
  573. suffix = is_vowel and "n" or ""
  574. if self.negative:
  575. assert not isinstance(self.obj, klass), (
  576. 'expected `{0}` to not be a{1} {2}'.format(
  577. self.obj, suffix, class_name))
  578. else:
  579. assert isinstance(self.obj, klass), (
  580. 'expected `{0}` to be a{1} {2}'.format(
  581. self.obj, suffix, class_name))
  582. return True
  583. a = an
  584. @assertionmethod
  585. def greater_than(self, dest):
  586. if self.negative:
  587. msg = "expected `{0}` to not be greater than `{1}`".format(
  588. self.obj, dest)
  589. assert not self.obj > dest, msg
  590. else:
  591. msg = "expected `{0}` to be greater than `{1}`".format(
  592. self.obj, dest)
  593. assert self.obj > dest, msg
  594. return True
  595. @assertionmethod
  596. def greater_than_or_equal_to(self, dest):
  597. if self.negative:
  598. msg = "expected `{0}` to not be greater than or equal to `{1}`".format(
  599. self.obj, dest)
  600. assert not self.obj >= dest, msg
  601. else:
  602. msg = "expected `{0}` to be greater than or equal to `{1}`".format(
  603. self.obj, dest)
  604. assert self.obj >= dest, msg
  605. return True
  606. @assertionmethod
  607. def lower_than(self, dest):
  608. if self.negative:
  609. msg = "expected `{0}` to not be lower than `{1}`".format(
  610. self.obj, dest)
  611. assert not self.obj < dest, msg
  612. else:
  613. msg = "expected `{0}` to be lower than `{1}`".format(
  614. self.obj, dest)
  615. assert self.obj < dest, msg
  616. return True
  617. @assertionmethod
  618. def lower_than_or_equal_to(self, dest):
  619. if self.negative:
  620. msg = "expected `{0}` to not be lower than or equal to `{1}`".format(
  621. self.obj, dest)
  622. assert not self.obj <= dest, msg
  623. else:
  624. msg = "expected `{0}` to be lower than or equal to `{1}`".format(
  625. self.obj, dest)
  626. assert self.obj <= dest, msg
  627. return True
  628. @assertionmethod
  629. def below(self, num):
  630. if self.negative:
  631. msg = "{0} should not be below {1}".format(self.obj, num)
  632. assert not self.obj < num, msg
  633. else:
  634. msg = "{0} should be below {1}".format(self.obj, num)
  635. assert self.obj < num, msg
  636. return True
  637. @assertionmethod
  638. def above(self, num):
  639. if self.negative:
  640. msg = "{0} should not be above {1}".format(self.obj, num)
  641. assert not self.obj > num, msg
  642. else:
  643. msg = "{0} should be above {1}".format(self.obj, num)
  644. assert self.obj > num, msg
  645. return True
  646. @assertionmethod
  647. def length_of(self, num):
  648. if self.negative:
  649. return self._that.len_is_not(num)
  650. return self._that.len_is(num)
  651. def called_with(self, *args, **kw):
  652. self._callable_args = args
  653. self._callable_kw = kw
  654. return self
  655. called = builtins.property(called_with)
  656. @assertionmethod
  657. def throw(self, *args, **kw):
  658. _that = AssertionHelper(self.obj,
  659. with_args=self._callable_args,
  660. and_kwargs=self._callable_kw)
  661. if self.negative:
  662. msg = ("{0} called with args {1} and kwargs {2} should "
  663. "not raise {3} but raised {4}")
  664. exc = args and args[0] or Exception
  665. try:
  666. self.obj(*self._callable_args, **self._callable_kw)
  667. return True
  668. except Exception as e:
  669. err = msg.format(
  670. self.obj,
  671. self._that._callable_args,
  672. self._that._callable_kw,
  673. exc,
  674. e,
  675. )
  676. raise AssertionError(err)
  677. return _that.raises(*args, **kw)
  678. thrown = throw
  679. raised = thrown
  680. @assertionmethod
  681. def return_value(self, value):
  682. return_value = self.obj(*self._callable_args, **self._callable_kw)
  683. return this(return_value).should.equal(value)
  684. returned_the_value = return_value
  685. @assertionmethod
  686. def look_like(self, value):
  687. if self.negative:
  688. try:
  689. self._that.looks_like(value)
  690. except AssertionError:
  691. return True
  692. else:
  693. msg = '%r should not look like %r but does'
  694. raise AssertionError(msg % (self.obj, value))
  695. return self._that.looks_like(value)
  696. @assertionmethod
  697. def contain(self, what):
  698. obj = self.obj
  699. if self.negative:
  700. return expect(what).to.not_be.within(obj)
  701. else:
  702. return expect(what).to.be.within(obj)
  703. @assertionmethod
  704. def match(self, regex, *args):
  705. obj_repr = repr(self.obj)
  706. assert isinstance(self.obj, basestring), (
  707. "{0} should be a string in order to compare using .match()".format(obj_repr)
  708. )
  709. matched = re.search(regex, self.obj, *args)
  710. modifiers_map = {
  711. re.I: "i",
  712. re.L: "l",
  713. re.M: "m",
  714. re.S: "s",
  715. re.U: "u",
  716. }
  717. modifiers = "".join([modifiers_map.get(x, "") for x in args])
  718. regex_representation = '/{0}/{1}'.format(regex, modifiers)
  719. if self.negative:
  720. assert matched is None, (
  721. "{0} should not match the regular expression {1}".format(
  722. obj_repr, regex_representation))
  723. else:
  724. assert matched is not None, (
  725. "{0} doesn't match the regular expression {1}".format(
  726. obj_repr, regex_representation))
  727. return True
  728. this = AssertionBuilder('this')
  729. the = AssertionBuilder('the')
  730. it = AssertionBuilder('it')
  731. these = AssertionBuilder('these')
  732. those = AssertionBuilder('those')
  733. expect = AssertionBuilder('expect')
  734. def assertion(func):
  735. """Extend sure with a custom assertion method."""
  736. func = assertionmethod(func)
  737. setattr(AssertionBuilder, func.__name__, func)
  738. return func
  739. def chain(func):
  740. """Extend sure with a custom chaining method."""
  741. setattr(AssertionBuilder, func.__name__, func)
  742. return func
  743. def chainproperty(func):
  744. """Extend sure with a custom chain property."""
  745. func = assertionproperty(func)
  746. setattr(AssertionBuilder, func.fget.__name__, func)
  747. return func
  748. class ensure(object):
  749. """
  750. Contextmanager to ensure that the given assertion message
  751. is printed upon a raised ``AssertionError`` exception.
  752. The ``args`` and ``kwargs`` are used to format
  753. the message using ``format()``.
  754. """
  755. def __init__(self, msg, *args, **kwargs):
  756. self.msg = msg
  757. self.args = args
  758. self.kwargs = kwargs
  759. def __enter__(self):
  760. return self
  761. def __exit__(self, exc_type, exc_value, traceback):
  762. """
  763. Catch all ``AsertionError`` exceptions and reraise
  764. them with the message provided to the context manager.
  765. """
  766. if exc_type is not AssertionError:
  767. return
  768. msg = self.msg.format(*self.args, **self.kwargs)
  769. raise AssertionError(msg)
  770. allows_new_syntax = not os.getenv('SURE_DISABLE_NEW_SYNTAX')
  771. if is_cpython and allows_new_syntax:
  772. def make_safe_property(method, name, should_be_property=True):
  773. if not should_be_property:
  774. return method(None)
  775. def deleter(method, self, *args, **kw):
  776. if isinstance(self, type):
  777. # if the attribute has to be deleted from a class object
  778. # we cannot use ``del self.__dict__[name]`` directly because we cannot
  779. # modify a mappingproxy object. Thus, we have to delete it in our
  780. # proxy __dict__.
  781. del overwritten_object_handlers[(id(self), method.__name__)]
  782. else:
  783. # if the attribute has to be deleted from an instance object
  784. # we are able to directly delete it from the object's __dict__.
  785. del self.__dict__[name]
  786. def setter(method, self, other):
  787. if isinstance(self, type):
  788. # if the attribute has to be set to a class object
  789. # we cannot use ``self.__dict__[name] = other`` directly because we cannot
  790. # modify a mappingproxy object. Thus, we have to set it in our
  791. # proxy __dict__.
  792. overwritten_object_handlers[(id(self), method.__name__)] = other
  793. else:
  794. # if the attribute has to be set to an instance object
  795. # we are able to directly set it in the object's __dict__.
  796. self.__dict__[name] = other
  797. return builtins.property(
  798. fget=method,
  799. fset=partial(setter, method),
  800. fdel=partial(deleter, method),
  801. )
  802. def build_assertion_property(name, is_negative, prop=True):
  803. """Build assertion property
  804. This is the assertion property which is usually patched
  805. to the built-in ``object`` and ``NoneType``.
  806. """
  807. def method(self):
  808. # check if the given object already has an attribute with the
  809. # given name. If yes return it instead of patching it.
  810. try:
  811. if name in self.__dict__:
  812. return self.__dict__[name]
  813. except AttributeError:
  814. # we do not have an object with __dict__, thus
  815. # it's safe to just continue and patch the `name`.
  816. pass
  817. overwritten_object_handler = overwritten_object_handlers.get((id(self), name), None)
  818. if overwritten_object_handler:
  819. return overwritten_object_handler
  820. builder = AssertionBuilder(name, negative=is_negative)
  821. instance = builder(self)
  822. callable_args = getattr(self, '_callable_args', ())
  823. if callable_args:
  824. instance._callable_args = callable_args
  825. callable_kw = getattr(self, '_callable_kw', {})
  826. if callable_kw:
  827. instance._callable_kw = callable_kw
  828. return instance
  829. method.__name__ = str(name)
  830. return make_safe_property(method, name, prop)
  831. object_handler = patchable_builtin(object)
  832. # We have to keep track of all objects which
  833. # should overwrite a ``POSITIVES`` or ``NEGATIVES``
  834. # property. If we wouldn't do that in the
  835. # make_safe_property.setter method we would loose
  836. # the newly assigned object reference.
  837. overwritten_object_handlers = {}
  838. # None does not have a tp_dict associated to its PyObject, so this
  839. # is the only way we could make it work like we expected.
  840. none = patchable_builtin(None.__class__)
  841. for name in POSITIVES:
  842. object_handler[name] = build_assertion_property(name, is_negative=False)
  843. none[name] = build_assertion_property(name, is_negative=False, prop=False)
  844. for name in NEGATIVES:
  845. object_handler[name] = build_assertion_property(name, is_negative=True)
  846. none[name] = build_assertion_property(name, is_negative=True, prop=False)
  847. old_dir = dir
  848. def enable():
  849. @wraps(builtins.dir)
  850. def _new_dir(*obj):
  851. if not obj:
  852. frame = inspect.currentframe()
  853. return sorted(frame.f_back.f_locals.keys())
  854. if len(obj) > 1:
  855. raise TypeError('dir expected at most 1 arguments, got {0}'.format(len(obj)))
  856. patched = [x for x in old_dir(obj[0]) if isinstance(getattr(obj[0], x, None), AssertionBuilder)]
  857. return sorted(set(old_dir(obj[0])).difference(patched))
  858. builtins.dir = _new_dir
  859. if allows_new_syntax:
  860. enable()