# -*- coding: utf-8 -*- # # Copyright (C) <2010-2017> Gabriel Falcão # # 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 . from __future__ import unicode_literals import os import re import traceback import inspect from copy import deepcopy from pprint import pformat from functools import wraps try: from collections import Iterable except ImportError: Iterable = (list, dict, tuple, set) try: import __builtin__ as builtins except ImportError: import builtins try: from re import Pattern except ImportError: Pattern = re._pattern_type from six import string_types, text_type from sure.core import DeepComparison from sure.core import _get_file_name from sure.core import _get_line_number from sure.core import itemize_length def identify_callable_location(callable_object): filename = os.path.relpath(callable_object.__code__.co_filename) lineno = callable_object.__code__.co_firstlineno callable_name = callable_object.__code__.co_name return '{0} [{1} line {2}]'.format(callable_name, filename, lineno).encode() def is_iterable(obj): return hasattr(obj, '__iter__') and not isinstance(obj, string_types) def all_integers(obj): if not is_iterable(obj): return for element in obj: if not isinstance(element, int): return return True def explanation(msg): def dec(func): @wraps(func) def wrap(self, what): ret = func(self, what) assert ret, msg % (self._src, what) return True return wrap return dec class AssertionHelper(object): def __init__(self, src, within_range=None, with_args=None, with_kwargs=None, and_kwargs=None): self._src = src self._attribute = None self._eval = None self._range = None if all_integers(within_range): if len(within_range) != 2: raise TypeError( 'within_range parameter must be a tuple with 2 objects', ) self._range = within_range self._callable_args = [] if isinstance(with_args, (list, tuple)): self._callable_args = list(with_args) self._callable_kw = {} if isinstance(with_kwargs, dict): self._callable_kw.update(with_kwargs) if isinstance(and_kwargs, dict): self._callable_kw.update(and_kwargs) @classmethod def is_a_matcher(cls, func): def match(self, *args, **kw): return func(self._src, *args, **kw) new_matcher = deepcopy(match) new_matcher.__name__ = func.__name__ setattr(cls, func.__name__, new_matcher) return new_matcher def raises(self, exc, msg=None): if not callable(self._src): raise TypeError('%r is not callable' % self._src) try: self._src(*self._callable_args, **self._callable_kw) except BaseException as e: if isinstance(exc, string_types): msg = exc exc = type(e) elif isinstance(exc, Pattern): msg = exc exc = type(e) err = text_type(e) if isinstance(exc, type) and issubclass(exc, BaseException): if not isinstance(e, exc): raise AssertionError( '%r should raise %r, but raised %r:\nORIGINAL EXCEPTION:\n\n%s' % ( self._src, exc, e.__class__, traceback.format_exc())) if isinstance(msg, string_types) and msg not in err: raise AssertionError(''' %r raised %s, but the exception message does not match.\n\nEXPECTED:\n%s\n\nGOT:\n%s'''.strip() % ( self._src, type(e).__name__, msg, err)) elif isinstance(msg, Pattern) and not msg.search(err): raise AssertionError( 'When calling %r the exception message does not match. ' \ 'Expected to match regex: %r\n against:\n %r' % (identify_callable_location(self._src), msg.pattern, err)) elif isinstance(msg, string_types) and msg not in err: raise AssertionError( 'When calling %r the exception message does not match. ' \ 'Expected: %r\n got:\n %r' % (self._src, msg, err)) elif isinstance(msg, Pattern) and not msg.search(err): raise AssertionError( 'When calling %r the exception message does not match. ' \ 'Expected to match regex: %r\n against:\n %r' % (identify_callable_location(self._src), msg.pattern, err)) else: raise e else: if inspect.isbuiltin(self._src): _src_filename = '' else: _src_filename = _get_file_name(self._src) if inspect.isfunction(self._src): _src_lineno = _get_line_number(self._src) raise AssertionError( 'calling function %s(%s at line: "%d") with args %r and kwargs %r did not raise %r' % ( self._src.__name__, _src_filename, _src_lineno, self._callable_args, self._callable_kw, exc)) else: raise AssertionError( 'at %s:\ncalling %s() with args %r and kwargs %r did not raise %r' % ( _src_filename, self._src.__name__, self._callable_args, self._callable_kw, exc)) return True def deep_equals(self, dst): deep = DeepComparison(self._src, dst) comparison = deep.compare() if isinstance(comparison, bool): return comparison raise comparison.as_assertion(self._src, dst) def equals(self, dst): if self._attribute and is_iterable(self._src): msg = '%r[%d].%s should be %r, but is %r' for index, item in enumerate(self._src): if self._range: if index < self._range[0] or index > self._range[1]: continue attribute = getattr(item, self._attribute) error = msg % ( self._src, index, self._attribute, dst, attribute) if attribute != dst: raise AssertionError(error) else: return self.deep_equals(dst) return True def looks_like(self, dst): old_src = pformat(self._src) old_dst = pformat(dst) self._src = re.sub(r'\s', '', self._src).lower() dst = re.sub(r'\s', '', dst).lower() error = '%s does not look like %s' % (old_src, old_dst) assert self._src == dst, error return self._src == dst def every_one_is(self, dst): msg = 'all members of %r should be %r, but the %dth is %r' for index, item in enumerate(self._src): if self._range: if index < self._range[0] or index > self._range[1]: continue error = msg % (self._src, dst, index, item) if item != dst: raise AssertionError(error) return True @explanation('%r should differ from %r, but is the same thing') def differs(self, dst): return self._src != dst @explanation('%r should be a instance of %r, but is not') def is_a(self, dst): return isinstance(self._src, dst) def at(self, key): assert self.has(key) if isinstance(self._src, dict): return AssertionHelper(self._src[key]) else: return AssertionHelper(getattr(self._src, key)) @explanation('%r should have %r, but have not') def has(self, that): return that in self def _get_that(self, that): try: that = int(that) except TypeError: that = len(that) return that def len_greater_than(self, that): that = self._get_that(that) length = len(self._src) if length <= that: error = 'the length of the %s should be greater then %d, but is %d' % ( type(self._src).__name__, that, length, ) raise AssertionError(error) return True def len_greater_than_or_equals(self, that): that = self._get_that(that) length = len(self._src) if length < that: error = 'the length of %r should be greater then or equals %d, but is %d' % ( self._src, that, length, ) raise AssertionError(error) return True def len_lower_than(self, that): original_that = that if isinstance(that, Iterable): that = len(that) else: that = self._get_that(that) length = len(self._src) if length >= that: error = 'the length of %r should be lower then %r, but is %d' % ( self._src, original_that, length, ) raise AssertionError(error) return True def len_lower_than_or_equals(self, that): that = self._get_that(that) length = len(self._src) error = 'the length of %r should be lower then or equals %d, but is %d' if length > that: msg = error % ( self._src, that, length, ) raise AssertionError(msg) return True def len_is(self, that): that = self._get_that(that) length = len(self._src) if length != that: error = 'the length of %r should be %d, but is %d' % ( self._src, that, length, ) raise AssertionError(error) return True def len_is_not(self, that): that = self._get_that(that) length = len(self._src) if length == that: error = 'the length of %r should not be %d' % ( self._src, that, ) raise AssertionError(error) return True def like(self, that): return self.has(that) def the_attribute(self, attr): self._attribute = attr return self def in_each(self, attr): self._eval = attr return self def matches(self, items): msg = '%r[%d].%s should be %r, but is %r' get_eval = lambda item: eval( "%s.%s" % ('current', self._eval), {}, {'current': item}, ) if self._eval and is_iterable(self._src): if isinstance(items, string_types): items = [items for x in range(len(items))] else: if len(items) != len(self._src): source = list(map(get_eval, self._src)) source_len = len(source) items_len = len(items) raise AssertionError( '%r has %d items, but the matching list has %d: %r' % (source, source_len, items_len, items), ) for index, (item, other) in enumerate(zip(self._src, items)): if self._range: if index < self._range[0] or index > self._range[1]: continue value = get_eval(item) error = msg % (self._src, index, self._eval, other, value) if other != value: raise AssertionError(error) else: return self.equals(items) return True @builtins.property def is_empty(self): try: lst = list(self._src) length = len(lst) assert length == 0, \ '%r is not empty, it has %s' % (self._src, itemize_length(self._src)) return True except TypeError: raise AssertionError("%r is not iterable" % self._src) @builtins.property def are_empty(self): return self.is_empty def __contains__(self, what): if isinstance(self._src, dict): items = self._src.keys() if isinstance(self._src, Iterable): items = self._src else: items = dir(self._src) return what in items def contains(self, what): assert what in self._src, '%r should be in %r' % (what, self._src) return True def does_not_contain(self, what): assert what not in self._src, \ '%r should NOT be in %r' % (what, self._src) return True doesnt_contain = does_not_contain that = AssertionHelper