123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- # -*- 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 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 = '<built-in function>'
- 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
|