12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091 |
- # -*- coding: utf-8 -*-
- # <sure - utility belt for automated testing in python>
- # Copyright (C) <2010-2017> Gabriel Falcão <gabriel@nacaolivre.org>
- #
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- from __future__ import unicode_literals
- import re
- import os
- import sys
- import difflib
- import inspect
- import traceback
- from functools import wraps, partial
- from datetime import datetime
- from six import string_types, text_type, PY2, get_function_code
- from six.moves import reduce
- from sure.old import AssertionHelper
- from sure.old import Iterable
- from sure.old import builtins
- from sure.core import DeepComparison
- from sure.core import DeepExplanation
- from sure.core import _get_file_name
- from sure.core import _get_line_number
- from sure.core import safe_repr
- from sure.core import anything # noqa
- from sure.magic import is_cpython, patchable_builtin
- from sure.registry import context as _registry
- if not PY2:
- basestring = str
- version = '1.4.11'
- not_here_error = \
- 'you have tried to access the attribute %r from the context ' \
- '(aka VariablesBag), but there is no such attribute assigned to it. ' \
- 'Maybe you misspelled it ? Well, here are the options: %s'
- original_obj_attrs = dir(object)
- class VariablesBag(dict):
- __varnames__ = None
- __sure_actions_ran__ = None
- __sure_action_results__ = None
- __sure_providers_of__ = None
- def __init__(self, *args, **kw):
- self.__varnames__ = []
- self.__sure_actions_ran__ = []
- self.__sure_action_results__ = []
- self.__sure_providers_of__ = {}
- return super(VariablesBag, self).__init__(*args, **kw)
- def __setattr__(self, attr, value):
- if attr not in dir(VariablesBag):
- self[attr] = value
- self.__varnames__.append(attr)
- return super(VariablesBag, self).__setattr__(attr, value)
- def __getattr__(self, attr):
- try:
- return super(VariablesBag, self).__getattribute__(attr)
- except AttributeError:
- if attr not in dir(VariablesBag):
- raise AssertionError(not_here_error % (
- attr,
- safe_repr(self.__varnames__),
- ))
- def ensure_type(caller_name, cast, obj):
- try:
- return cast(obj)
- except TypeError:
- raise AssertionError('{0} tried to ')
- class CallBack(object):
- context_error = "the function %s defined at %s line %d, is being "\
- "decorated by either @that_with_context or @scenario, so it should " \
- "take at least 1 parameter, which is the test context"
- def __init__(self, cb, args, kwargs):
- self.callback = cb
- self.args = args or []
- self.kwargs = kwargs or {}
- self.callback_name = cb.__name__
- self.callback_filename = os.path.split(get_function_code(cb).co_filename)[-1]
- self.callback_lineno = get_function_code(cb).co_firstlineno + 1
- def apply(self, *optional_args):
- args = list(optional_args)
- args.extend(self.args)
- try:
- return self.callback(*args, **self.kwargs)
- except Exception:
- exc_klass, exc_value, tb = sys.exc_info()
- err = traceback.format_exc().splitlines()[-1]
- err = err.replace('{0}:'.format(exc_klass.__name__), '').strip()
- if err.startswith(self.callback_name) and \
- ('takes no arguments (1 given)' in err or
- 'takes 0 positional arguments but 1 was given' in err):
- raise TypeError(self.context_error % (
- self.callback_name,
- self.callback_filename,
- self.callback_lineno,
- )
- )
- raise
- def that_with_context(setup=None, teardown=None):
- def dec(func):
- @wraps(func)
- def wrap(*args, **kw):
- context = VariablesBag()
- if callable(setup):
- cb = CallBack(setup, args, kw)
- cb.apply(context)
- elif isinstance(setup, Iterable):
- for s in setup:
- cb = CallBack(s, args, kw)
- cb.apply(context)
- test = CallBack(func, args, kw)
- try:
- res = test.apply(context)
- finally:
- if callable(teardown):
- cb = CallBack(teardown, args, kw)
- cb.apply(context)
- elif isinstance(teardown, Iterable):
- for s in teardown:
- cb = CallBack(s, args, kw)
- cb.apply(context)
- return res
- return wrap
- return dec
- scenario = that_with_context
- def within(**units):
- assert len(units) == 1, 'use within(number=unit). e.g.: within(one=second)'
- word, unit = list(units.items())[0]
- value = word_to_number(word)
- convert_from, convert_to = UNITS[unit]
- timeout = convert_from(value)
- exc = []
- def dec(func):
- def wrap(*args, **kw):
- start = datetime.utcnow()
- try:
- func(start, *args, **kw)
- except TypeError as e:
- if PY2:
- # PY2 has different error message
- fmt = '{0}() takes no arguments'
- else:
- fmt = '{0}() takes 0 positional arguments but 1 was given'
- err = text_type(e)
- if fmt.format(func.__name__) in err:
- func(*args, **kw)
- else:
- exc.append(traceback.format_exc())
- except Exception as e:
- exc.append(traceback.format_exc())
- end = datetime.utcnow()
- delta = (end - start)
- took = convert_to(delta.microseconds)
- print(took, timeout)
- assert took < timeout, \
- '%s did not run within %s %s' % (func.__name__, word, unit)
- if exc:
- raise AssertionError(exc.pop(0))
- wrap.__name__ = func.__name__
- wrap.__doc__ = func.__doc__
- wrap.__dict__ = func.__dict__
- return wrap
- return dec
- UNITS = {
- 'minutes': (
- lambda from_num: from_num / 60.0,
- lambda to_num: to_num * 6000000,
- ),
- 'seconds': (
- lambda from_num: from_num,
- lambda to_num: to_num / 100000,
- ),
- 'miliseconds': (
- lambda from_num: from_num * 1000,
- lambda to_num: to_num / 100,
- ),
- 'microseconds': (
- lambda from_num: from_num * 100000,
- lambda to_num: to_num,
- ),
- }
- milisecond = miliseconds = 'miliseconds'
- microsecond = microseconds = 'microseconds'
- second = seconds = 'seconds'
- minute = minutes = 'minutes'
- def word_to_number(word):
- basic = {
- 'one': 1,
- 'two': 2,
- 'three': 3,
- 'four': 4,
- 'five': 5,
- 'six': 6,
- 'seven': 7,
- 'eight': 8,
- 'nine': 9,
- 'ten': 10,
- 'eleven': 11,
- 'twelve': 12,
- }
- try:
- return basic[word]
- except KeyError:
- raise AssertionError(
- 'sure supports only literal numbers from one to twelve, ' \
- 'you tried the word "twenty"')
- def action_for(context, provides=None, depends_on=None):
- if not provides:
- provides = []
- if not depends_on:
- depends_on = []
- def register_providers(func, attr):
- if re.search(r'^[{]\d+[}]$', attr):
- return # ignore dinamically declared provides
- if not attr in context.__sure_providers_of__:
- context.__sure_providers_of__[attr] = []
- context.__sure_providers_of__[attr].append(func)
- def register_dinamic_providers(func, attr, args, kwargs):
- found = re.search(r'^[{](\d+)[}]$', attr)
- if not found:
- return # ignore dinamically declared provides
- index = int(found.group(1))
- assert index < len(args), \
- 'the dinamic provider index: {%d} is bigger than %d, which is ' \
- 'the length of the positional arguments passed to %s' % (
- index, len(args), func.__name__)
- attr = args[index]
- if not attr in context.__sure_providers_of__:
- context.__sure_providers_of__[attr] = []
- context.__sure_providers_of__[attr].append(func)
- def ensure_providers(func, attr, args, kwargs):
- found = re.search(r'^[{](\d+)[}]$', attr)
- if found:
- index = int(found.group(1))
- attr = args[index]
- assert attr in context, \
- 'the action "%s" was supposed to provide the attribute "%s" ' \
- 'into the context, but it did not. Please double check its ' \
- 'implementation' % (func.__name__, attr)
- dependency_error_lonely = 'the action "%s" defined at %s:%d ' \
- 'depends on the attribute "%s" to be available in the' \
- ' context. It turns out that there are no actions providing ' \
- 'that. Please double-check the implementation'
- dependency_error_hints = 'the action "%s" defined at %s:%d ' \
- 'depends on the attribute "%s" to be available in the context.'\
- ' You need to call one of the following actions beforehand:\n'
- def check_dependencies(func):
- action = func.__name__
- filename = _get_file_name(func)
- lineno = _get_line_number(func)
- for dependency in depends_on:
- if dependency in context.__sure_providers_of__:
- providers = context.__sure_providers_of__[dependency]
- err = dependency_error_hints % (
- action,
- filename,
- lineno,
- dependency,
- )
- err += '\n'.join([
- ' -> %s at %s:%d' % (
- p.__name__,
- _get_file_name(p),
- _get_line_number(p)) for p in providers])
- else:
- err = dependency_error_lonely % (
- action,
- filename,
- lineno,
- dependency,
- )
- assert dependency in context, err
- def decorate_and_absorb(func):
- [register_providers(func, attr) for attr in provides]
- @wraps(func)
- def wrapper(*args, **kw):
- [register_dinamic_providers(func, attr, args, kw)
- for attr in provides]
- context.__sure_actions_ran__.append((func, args, kw))
- check_dependencies(func)
- result = func(*args, **kw)
- [ensure_providers(func, attr, args, kw) for attr in provides]
- context.__sure_action_results__.append(result)
- return context
- setattr(context, func.__name__, wrapper)
- return wrapper
- return decorate_and_absorb
- def work_in_progress(func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- _registry['is_running'] = True
- ret = func(*args, **kwargs)
- _registry['is_running'] = False
- return ret
- return wrapper
- def assertionmethod(func):
- @wraps(func)
- def wrapper(self, *args, **kw):
- try:
- value = func(self, *args, **kw)
- except AssertionError as e:
- raise AssertionError(e)
- msg = "{0}({1}) failed".format(
- func.__name__,
- ", ".join(map(safe_repr, args)),
- ", ".join(["{0}={1}".format(k, safe_repr(kw[k])) for k in kw]),
- )
- if PY2:
- msg = text_type(msg)
- assert value, msg
- return value
- return wrapper
- def assertionproperty(func):
- return builtins.property(assertionmethod(func))
- POSITIVES = [
- 'should',
- 'does',
- 'do',
- 'must',
- 'when',
- ]
- NEGATIVES = [
- 'shouldnt',
- 'dont',
- 'do_not',
- 'doesnt',
- 'does_not',
- 'doesnot',
- 'should_not',
- 'shouldnot',
- ]
- class IdentityAssertion(object):
- def __init__(self, assertion_builder):
- self._ab = assertion_builder
- def __call__(self, other):
- if self._ab.negative:
- assert self._ab.obj is not other, "{0} should not be the same object as {1}, but it is".format(self._ab.obj, other)
- return True
- assert self._ab.obj is other, "{0} should be the same object as {1}, but it is not".format(self._ab.obj, other)
- return True
- def __getattr__(self, name):
- return getattr(self._ab, name)
- class AssertionBuilder(object):
- def __init__(self, name=None, negative=False, obj=None, callable_args=None, callable_kw=None):
- self._name = name
- self.negative = negative
- self.obj = obj
- self._callable_args = callable_args or []
- self._callable_kw = callable_kw or {}
- self._that = AssertionHelper(self.obj)
- def __call__(self, obj):
- self.obj = obj
- if isinstance(obj, self.__class__):
- self.obj = obj.obj
- self._callable_args = obj._callable_args
- self._callable_kw = obj._callable_kw
- self._that = AssertionHelper(self.obj)
- return self
- def __getattr__(self, attr):
- special_case = False
- special_case = attr in (POSITIVES + NEGATIVES)
- negative = attr in NEGATIVES
- if special_case:
- return AssertionBuilder(attr, negative=negative, obj=self.obj,
- callable_args=self._callable_args, callable_kw=self._callable_kw)
- return super(AssertionBuilder, self).__getattribute__(attr)
- @assertionproperty
- def callable(self):
- if self.negative:
- assert not callable(self.obj), (
- 'expected `{0}` to not be callable but it is'.format(safe_repr(self.obj)))
- else:
- assert callable(self.obj), (
- 'expected {0} to be callable'.format(safe_repr(self.obj)))
- return True
- @assertionproperty
- def be(self):
- return IdentityAssertion(self)
- being = be
- @assertionproperty
- def not_be(self):
- return IdentityAssertion(self.should_not)
- not_being = not_be
- @assertionproperty
- def not_have(self):
- return self.should_not
- @assertionproperty
- def to_not(self):
- return self.should_not
- @assertionproperty
- def to(self):
- return self
- which = to
- @assertionproperty
- def when(self):
- return self
- @assertionproperty
- def have(self):
- return self
- @assertionproperty
- def with_value(self):
- return self
- def property(self, name):
- has_it = hasattr(self.obj, name)
- if self.negative:
- assert not has_it, (
- '%r should not have the property `%s`, '
- 'but it is %r' % (self.obj, name, getattr(self.obj, name)))
- return True
- assert has_it, (
- "%r should have the property `%s` but does not" % (
- self.obj, name))
- return expect(getattr(self.obj, name))
- def key(self, name):
- has_it = name in self.obj
- if self.negative:
- assert not has_it, (
- '%r should not have the key `%s`, '
- 'but it is %r' % (self.obj, name, self.obj[name]))
- return True
- assert has_it, (
- "%r should have the key `%s` but does not" % (
- self.obj, name))
- return expect(self.obj[name])
- @assertionproperty
- def empty(self):
- representation = safe_repr(self.obj)
- length = len(self.obj)
- if self.negative:
- assert length > 0, (
- "expected `{0}` to not be empty".format(representation))
- else:
- assert length == 0, (
- "expected `{0}` to be empty but it has {1} items".format(representation, length))
- return True
- @assertionproperty
- def ok(self):
- if self.negative:
- msg = 'expected `{0}` to be falsy'.format(self.obj)
- assert not bool(self.obj), msg
- else:
- msg = 'expected `{0}` to be truthy'.format(self.obj)
- assert bool(self.obj), msg
- return True
- truthy = ok
- true = ok
- @assertionproperty
- def falsy(self):
- if self.negative:
- msg = 'expected `{0}` to be truthy'.format(self.obj)
- assert bool(self.obj), msg
- else:
- msg = 'expected `{0}` to be falsy'.format(self.obj)
- assert not bool(self.obj), msg
- return True
- false = falsy
- @assertionproperty
- def none(self):
- if self.negative:
- assert self.obj is not None, (
- r"expected `{0}` to not be None".format(self.obj))
- else:
- assert self.obj is None, (
- r"expected `{0}` to be None".format(self.obj))
- return True
- @assertionmethod
- def within_range(self, start, end):
- start = ensure_type('within_range', int, start)
- end = ensure_type('within_range', int, end)
- subject = ensure_type('within_range', int, self.obj)
- is_within_range = subject >= start and subject <= end
- if self.negative:
- if is_within_range:
- raise AssertionError('expected {0} to NOT be within {1} and {2}'.format(subject, start, end))
- return not is_within_range
- else:
- if not is_within_range:
- raise AssertionError('expected {0} to be within {1} and {2}'.format(subject, start, end))
- return is_within_range
- @assertionmethod
- def within(self, first, *rest):
- if isinstance(first, Iterable):
- collection_should = AssertionHelper(first)
- if self.negative:
- return collection_should.does_not_contain(self.obj)
- else:
- return collection_should.contains(self.obj)
- elif len(rest) == 1:
- return self.within_range(first, rest[0])
- else:
- if self.negative:
- ppath = '{0}.should_not.be.within'.format(self.obj)
- else:
- ppath = '{0}.should.be.within'.format(self.obj)
- raise AssertionError((
- '{0}({1}, {2}) must be called with either a iterable:\n'
- '{0}([1, 2, 3, 4])\n'
- 'or with a range of numbers:'
- '{0}(1, 3000)'
- ).format(ppath, first, ", ".join([repr(x) for x in rest])))
- @assertionmethod
- def equal(self, what, epsilon=None):
- """compares given object ``X`` with an expected ``Y`` object.
- It primarily assures that the compared objects are absolute equal ``==``.
- :param what: the expected value
- :param epsilon: a delta to leverage upper-bound floating point permissiveness
- """
- try:
- comparison = DeepComparison(self.obj, what, epsilon).compare()
- error = False
- except AssertionError as e:
- error = e
- comparison = None
- if isinstance(comparison, DeepExplanation):
- error = comparison.get_assertion(self.obj, what)
- if self.negative:
- if error:
- return True
- msg = '%s should differ from %s, but is the same thing'
- raise AssertionError(msg % (safe_repr(self.obj), safe_repr(what)))
- else:
- if not error:
- return True
- raise error
- eql = equal
- equals = equal
- equal_to = equal
- @assertionmethod
- def different_of(self, what):
- differ = difflib.Differ()
- source = self.obj.strip().splitlines(True)
- destination = what.strip().splitlines(True)
- result = differ.compare(source, destination)
- difference = "".join(result)
- if self.negative:
- if self.obj != what:
- assert not difference, "Difference:\n\n{0}".format(difference)
- else:
- if self.obj == what:
- raise AssertionError("{0} should be different of {1}".format(self.obj, what))
- return True
- @assertionmethod
- def an(self, klass):
- if isinstance(klass, type):
- class_name = klass.__name__
- elif isinstance(klass, string_types):
- class_name = klass.strip()
- else:
- class_name = text_type(klass)
- is_vowel = class_name[0] in 'aeiou'
- if isinstance(klass, string_types):
- if '.' in klass:
- items = klass.split('.')
- first = items.pop(0)
- if not items:
- items = [first]
- first = '_abcoll'
- else:
- if sys.version_info <= (3, 0, 0):
- first = '__builtin__'
- else:
- first = 'builtins'
- items = [klass]
- klass = reduce(getattr, items, __import__(first))
- suffix = is_vowel and "n" or ""
- if self.negative:
- assert not isinstance(self.obj, klass), (
- 'expected `{0}` to not be a{1} {2}'.format(
- self.obj, suffix, class_name))
- else:
- assert isinstance(self.obj, klass), (
- 'expected `{0}` to be a{1} {2}'.format(
- self.obj, suffix, class_name))
- return True
- a = an
- @assertionmethod
- def greater_than(self, dest):
- if self.negative:
- msg = "expected `{0}` to not be greater than `{1}`".format(
- self.obj, dest)
- assert not self.obj > dest, msg
- else:
- msg = "expected `{0}` to be greater than `{1}`".format(
- self.obj, dest)
- assert self.obj > dest, msg
- return True
- @assertionmethod
- def greater_than_or_equal_to(self, dest):
- if self.negative:
- msg = "expected `{0}` to not be greater than or equal to `{1}`".format(
- self.obj, dest)
- assert not self.obj >= dest, msg
- else:
- msg = "expected `{0}` to be greater than or equal to `{1}`".format(
- self.obj, dest)
- assert self.obj >= dest, msg
- return True
- @assertionmethod
- def lower_than(self, dest):
- if self.negative:
- msg = "expected `{0}` to not be lower than `{1}`".format(
- self.obj, dest)
- assert not self.obj < dest, msg
- else:
- msg = "expected `{0}` to be lower than `{1}`".format(
- self.obj, dest)
- assert self.obj < dest, msg
- return True
- @assertionmethod
- def lower_than_or_equal_to(self, dest):
- if self.negative:
- msg = "expected `{0}` to not be lower than or equal to `{1}`".format(
- self.obj, dest)
- assert not self.obj <= dest, msg
- else:
- msg = "expected `{0}` to be lower than or equal to `{1}`".format(
- self.obj, dest)
- assert self.obj <= dest, msg
- return True
- @assertionmethod
- def below(self, num):
- if self.negative:
- msg = "{0} should not be below {1}".format(self.obj, num)
- assert not self.obj < num, msg
- else:
- msg = "{0} should be below {1}".format(self.obj, num)
- assert self.obj < num, msg
- return True
- @assertionmethod
- def above(self, num):
- if self.negative:
- msg = "{0} should not be above {1}".format(self.obj, num)
- assert not self.obj > num, msg
- else:
- msg = "{0} should be above {1}".format(self.obj, num)
- assert self.obj > num, msg
- return True
- @assertionmethod
- def length_of(self, num):
- if self.negative:
- return self._that.len_is_not(num)
- return self._that.len_is(num)
- def called_with(self, *args, **kw):
- self._callable_args = args
- self._callable_kw = kw
- return self
- called = builtins.property(called_with)
- @assertionmethod
- def throw(self, *args, **kw):
- _that = AssertionHelper(self.obj,
- with_args=self._callable_args,
- and_kwargs=self._callable_kw)
- if self.negative:
- msg = ("{0} called with args {1} and kwargs {2} should "
- "not raise {3} but raised {4}")
- exc = args and args[0] or Exception
- try:
- self.obj(*self._callable_args, **self._callable_kw)
- return True
- except Exception as e:
- err = msg.format(
- self.obj,
- self._that._callable_args,
- self._that._callable_kw,
- exc,
- e,
- )
- raise AssertionError(err)
- return _that.raises(*args, **kw)
- thrown = throw
- raised = thrown
- @assertionmethod
- def return_value(self, value):
- return_value = self.obj(*self._callable_args, **self._callable_kw)
- return this(return_value).should.equal(value)
- returned_the_value = return_value
- @assertionmethod
- def look_like(self, value):
- if self.negative:
- try:
- self._that.looks_like(value)
- except AssertionError:
- return True
- else:
- msg = '%r should not look like %r but does'
- raise AssertionError(msg % (self.obj, value))
- return self._that.looks_like(value)
- @assertionmethod
- def contain(self, what):
- obj = self.obj
- if self.negative:
- return expect(what).to.not_be.within(obj)
- else:
- return expect(what).to.be.within(obj)
- @assertionmethod
- def match(self, regex, *args):
- obj_repr = repr(self.obj)
- assert isinstance(self.obj, basestring), (
- "{0} should be a string in order to compare using .match()".format(obj_repr)
- )
- matched = re.search(regex, self.obj, *args)
- modifiers_map = {
- re.I: "i",
- re.L: "l",
- re.M: "m",
- re.S: "s",
- re.U: "u",
- }
- modifiers = "".join([modifiers_map.get(x, "") for x in args])
- regex_representation = '/{0}/{1}'.format(regex, modifiers)
- if self.negative:
- assert matched is None, (
- "{0} should not match the regular expression {1}".format(
- obj_repr, regex_representation))
- else:
- assert matched is not None, (
- "{0} doesn't match the regular expression {1}".format(
- obj_repr, regex_representation))
- return True
- this = AssertionBuilder('this')
- the = AssertionBuilder('the')
- it = AssertionBuilder('it')
- these = AssertionBuilder('these')
- those = AssertionBuilder('those')
- expect = AssertionBuilder('expect')
- def assertion(func):
- """Extend sure with a custom assertion method."""
- func = assertionmethod(func)
- setattr(AssertionBuilder, func.__name__, func)
- return func
- def chain(func):
- """Extend sure with a custom chaining method."""
- setattr(AssertionBuilder, func.__name__, func)
- return func
- def chainproperty(func):
- """Extend sure with a custom chain property."""
- func = assertionproperty(func)
- setattr(AssertionBuilder, func.fget.__name__, func)
- return func
- class ensure(object):
- """
- Contextmanager to ensure that the given assertion message
- is printed upon a raised ``AssertionError`` exception.
- The ``args`` and ``kwargs`` are used to format
- the message using ``format()``.
- """
- def __init__(self, msg, *args, **kwargs):
- self.msg = msg
- self.args = args
- self.kwargs = kwargs
- def __enter__(self):
- return self
- def __exit__(self, exc_type, exc_value, traceback):
- """
- Catch all ``AsertionError`` exceptions and reraise
- them with the message provided to the context manager.
- """
- if exc_type is not AssertionError:
- return
- msg = self.msg.format(*self.args, **self.kwargs)
- raise AssertionError(msg)
- allows_new_syntax = not os.getenv('SURE_DISABLE_NEW_SYNTAX')
- if is_cpython and allows_new_syntax:
- def make_safe_property(method, name, should_be_property=True):
- if not should_be_property:
- return method(None)
- def deleter(method, self, *args, **kw):
- if isinstance(self, type):
- # if the attribute has to be deleted from a class object
- # we cannot use ``del self.__dict__[name]`` directly because we cannot
- # modify a mappingproxy object. Thus, we have to delete it in our
- # proxy __dict__.
- del overwritten_object_handlers[(id(self), method.__name__)]
- else:
- # if the attribute has to be deleted from an instance object
- # we are able to directly delete it from the object's __dict__.
- del self.__dict__[name]
- def setter(method, self, other):
- if isinstance(self, type):
- # if the attribute has to be set to a class object
- # we cannot use ``self.__dict__[name] = other`` directly because we cannot
- # modify a mappingproxy object. Thus, we have to set it in our
- # proxy __dict__.
- overwritten_object_handlers[(id(self), method.__name__)] = other
- else:
- # if the attribute has to be set to an instance object
- # we are able to directly set it in the object's __dict__.
- self.__dict__[name] = other
- return builtins.property(
- fget=method,
- fset=partial(setter, method),
- fdel=partial(deleter, method),
- )
- def build_assertion_property(name, is_negative, prop=True):
- """Build assertion property
- This is the assertion property which is usually patched
- to the built-in ``object`` and ``NoneType``.
- """
- def method(self):
- # check if the given object already has an attribute with the
- # given name. If yes return it instead of patching it.
- try:
- if name in self.__dict__:
- return self.__dict__[name]
- except AttributeError:
- # we do not have an object with __dict__, thus
- # it's safe to just continue and patch the `name`.
- pass
- overwritten_object_handler = overwritten_object_handlers.get((id(self), name), None)
- if overwritten_object_handler:
- return overwritten_object_handler
- builder = AssertionBuilder(name, negative=is_negative)
- instance = builder(self)
- callable_args = getattr(self, '_callable_args', ())
- if callable_args:
- instance._callable_args = callable_args
- callable_kw = getattr(self, '_callable_kw', {})
- if callable_kw:
- instance._callable_kw = callable_kw
- return instance
- method.__name__ = str(name)
- return make_safe_property(method, name, prop)
- object_handler = patchable_builtin(object)
- # We have to keep track of all objects which
- # should overwrite a ``POSITIVES`` or ``NEGATIVES``
- # property. If we wouldn't do that in the
- # make_safe_property.setter method we would loose
- # the newly assigned object reference.
- overwritten_object_handlers = {}
- # None does not have a tp_dict associated to its PyObject, so this
- # is the only way we could make it work like we expected.
- none = patchable_builtin(None.__class__)
- for name in POSITIVES:
- object_handler[name] = build_assertion_property(name, is_negative=False)
- none[name] = build_assertion_property(name, is_negative=False, prop=False)
- for name in NEGATIVES:
- object_handler[name] = build_assertion_property(name, is_negative=True)
- none[name] = build_assertion_property(name, is_negative=True, prop=False)
- old_dir = dir
- def enable():
- @wraps(builtins.dir)
- def _new_dir(*obj):
- if not obj:
- frame = inspect.currentframe()
- return sorted(frame.f_back.f_locals.keys())
- if len(obj) > 1:
- raise TypeError('dir expected at most 1 arguments, got {0}'.format(len(obj)))
- patched = [x for x in old_dir(obj[0]) if isinstance(getattr(obj[0], x, None), AssertionBuilder)]
- return sorted(set(old_dir(obj[0])).difference(patched))
- builtins.dir = _new_dir
- if allows_new_syntax:
- enable()
|