Skip to content

Commit

Permalink
presents more test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielfalcao committed Jan 19, 2024
1 parent 0027034 commit 9bfded6
Show file tree
Hide file tree
Showing 15 changed files with 404 additions and 222 deletions.
3 changes: 3 additions & 0 deletions docs/source/api-reference.rst
Expand Up @@ -95,7 +95,10 @@ API Reference
------------------------

.. autoclass:: sure.doubles.dummies.Anything
.. autoclass:: sure.doubles.dummies.AnythingOfType
.. autoattribute:: sure.doubles.dummies.anything
.. autofunction:: sure.doubles.dummies.anything_of_type


``sure.doubles.fakes``
----------------------
Expand Down
11 changes: 11 additions & 0 deletions docs/source/assertion-reference.rst
Expand Up @@ -3,6 +3,17 @@
Assertion Builder Reference
===========================

Aliases
-------

.. code:: python
from sure import expects
expects("text").to.equal("text")
expects.that("text").equals("text")
Numerical Equality
------------------

Expand Down
2 changes: 1 addition & 1 deletion docs/source/changelog.rst
Expand Up @@ -52,7 +52,7 @@ Fixed
Fixed
~~~~~

- Reading the version dinamically was causing import errors that caused
- Reading the version dynamically was causing import errors that caused
error when installing package. Refs #144

`v1.4.7 <https://github.com/gabrielfalcao/sure/compare/1.4.6...v1.4.7>`__
Expand Down
108 changes: 52 additions & 56 deletions sure/__init__.py
Expand Up @@ -20,14 +20,14 @@
import re
import os
import sys

import builtins
import difflib
import inspect
import traceback

import operator
from functools import wraps, partial, reduce
from datetime import datetime
from typing import Dict, List, Optional, Tuple, Union

from sure.original import AssertionHelper
from sure.original import Iterable
Expand Down Expand Up @@ -336,18 +336,41 @@ def word_to_number(word):
"fourteen": 14,
"fifteen": 15,
"sixteen": 16,
"seventeen": 17,
"eighteen": 18,
"nineteen": 19,
"twenty": 20,
"thirty": 30,
"fourty": 40,
"fifty": 50,
"sixty": 60,
"seventy": 70,
"eighty": 80,
"ninety": 90,
"hundred": 100,
"thousand": 1000,
"million": 1000000,
}
# TODO: refactor
try:
return basic[word]
except KeyError:
raise AssertionError(
"sure supports only literal numbers from one to sixteen, "
f'you tried the word "{word}"'
)
value = int(False)
words = word.split("_")
for p, word in enumerate(words):
number = basic.get(words[p], 0)
if len(words) > p + 1:
next_number = basic.get(words[p + 1], 0)
else:
next_number = 0

if number <= 10 and next_number > 90:
value += number * next_number
elif number > 90:
continue
else:
value += number

return value


def action_for(context, provides=None, depends_on=None):
def action_for(context, provides=None, depends_on=None): # pragma: no cover # TODO: add test coverage
"""function decorator for defining functions which might provide a
list of assets to the staging area and might declare a list of
dependencies expected to exist within a :class:`StagingArea`
Expand Down Expand Up @@ -478,20 +501,10 @@ def assertionmethod(func):
@wraps(func)
def wrapper(self, *args, **kw):
try:
value = func(self, *args, **kw)
return func(self, *args, **kw)
except AssertionError as e:
raise e

if not value:
raise AssertionError(
f"{0}({1}{2}) failed".format(
func.__name__,
", ".join(map(repr, args)),
", ".join(["{0}={1}".format(k, repr(kw[k])) for k in kw]),
)
)
return value

return wrapper


Expand All @@ -503,12 +516,12 @@ def assertionproperty(func):
class AssertionBuilder(object):
def __init__(
self,
name=None,
negative=False,
actual=None,
with_args=None,
with_kws=None,
and_kws=None
name: str,
negative: bool = False,
actual: object = None,
with_args: Optional[Union[list, tuple]] = None,
with_kws: Optional[Dict[str, object]] = None,
and_kws: Optional[Dict[str, object]] = None,
):
self._name = name
self.negative = negative
Expand Down Expand Up @@ -557,25 +570,10 @@ def __call__(self,
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,
actual=self.actual,
with_args=self._callable_args,
with_kws=self._callable_kw,
)

try:
return getattr(self._that, attr)
except AttributeError:
return self.__getattribute__(attr)
return super(AssertionBuilder, self).__getattribute__(attr)
return super(AssertionBuilder, self).__getattribute__(attr)

@assertionproperty
def callable(self):
Expand Down Expand Up @@ -619,23 +617,23 @@ def to_not(self):
return self.should_not

@assertionproperty
def to(self):
def have(self):
return self

@assertionproperty
def when(self):
def which(self):
return self

@assertionproperty
def which(self):
def to(self):
return self

@assertionproperty
def have(self):
def when(self):
return self

@assertionproperty
def with_value(self):
def that(self):
return self

@assertionmethod
Expand Down Expand Up @@ -1023,10 +1021,6 @@ def called_with(self, *args, **kw):

@assertionmethod
def throw(self, *args, **kw):
_that = AssertionHelper(
self.actual, with_args=self._callable_args, and_kws=self._callable_kw
)

if self.negative:
msg = (
"{0} called with args {1} and keyword-args {2} should "
Expand All @@ -1040,14 +1034,16 @@ def throw(self, *args, **kw):
except Exception as e:
err = msg.format(
self.actual,
self._that._callable_args,
self._that._callable_kw,
self._callable_args,
self._callable_kw,
exc,
e,
)
raise AssertionError(err)

return _that.raises(*args, **kw)
return AssertionHelper(
self.actual, with_args=self._callable_args, and_kws=self._callable_kw
).raises(*args, **kw)

thrown = throw
raises = thrown
Expand Down
5 changes: 3 additions & 2 deletions sure/cli.py
Expand Up @@ -100,6 +100,9 @@ def entrypoint(
"source": cover_module,
}

options = RuntimeOptions(immediate=immediate, ignore=ignore, reap_warnings=reap_warnings)
runner = Runner(resolve_path(os.getcwd()), reporter, options)

cov = with_coverage and coverage.Coverage(**coverageopts) or None
if cov:
cover_erase and cov.erase()
Expand All @@ -109,8 +112,6 @@ def entrypoint(
if special_syntax:
sure.enable_special_syntax()

options = RuntimeOptions(immediate=immediate, ignore=ignore, reap_warnings=reap_warnings)
runner = Runner(resolve_path(os.getcwd()), reporter, options)
try:
result = runner.run(paths)
except Exception as e:
Expand Down
35 changes: 20 additions & 15 deletions sure/loader/__init__.py
Expand Up @@ -14,7 +14,6 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import os
import sys
import ast
Expand All @@ -33,12 +32,12 @@
FileSystemError,
CallerLocation,
collapse_path,
send_runtime_warning
send_runtime_warning,
)

from .astutil import gather_class_definitions_from_module_path

__MODULES__ = {}
__MODULE_SPECS__ = {}
__ROOTS__ = {}
__TEST_CLASSES__ = {}

Expand All @@ -61,7 +60,6 @@ def resolve_path(path, relative_to="~") -> Path:

def get_package(path: Union[str, Path]) -> Path:
path = Path(path).expanduser().absolute()

if not path.is_dir():
path = path.parent

Expand Down Expand Up @@ -100,7 +98,9 @@ def from_function_or_method(cls, target):
name = target.__name__
else:
name = target.__class__.__name__
path, lineno = get_type_definition_filename_and_firstlineno(target.__class__)
path, lineno = get_type_definition_filename_and_firstlineno(
target.__class__
)

return cls(
filename=path,
Expand All @@ -120,7 +120,7 @@ def get_type_definition_filename_and_firstlineno(type_object: type) -> Tuple[Pat
raise RuntimeError(
f"module `{module_name}' does not appear within `sys.modules'. Perhaps Sure is not being used the right way or there is a bug in the current version",
)
if not hasattr(module, '__file__'):
if not hasattr(module, "__file__"):
return f"<{module_name}>", -1

path = Path(module.__file__)
Expand All @@ -145,7 +145,9 @@ def load_recursive(
modules = []
excludes = excludes or []
if not isinstance(excludes, list):
raise TypeError(f"sure.loader.load_recursive() param `excludes' must be a {list} but is {repr(excludes)} ({type(excludes)}) instead")
raise TypeError(
f"sure.loader.load_recursive() param `excludes' must be a {list} but is {repr(excludes)} ({type(excludes)}) instead"
)
path = Path(path)
if path.is_file():
if fnmatch(path, glob_pattern):
Expand Down Expand Up @@ -185,6 +187,11 @@ def load_python_path(cls, path: Union[str, Path]) -> List[types.ModuleType]:
send_runtime_warning(f"ignoring {path} for seeming to be a __dunder__ file")
return []

if path.is_symlink() and not path.resolve().exists():
# avoid loading symlinks such as Emacs temporary files .i.e: `.#*'
send_runtime_warning(f"parsing skipped of irregular file `{path.absolute()}'")
return []

module, root = cls.load_package(path)
return [module]

Expand All @@ -202,6 +209,7 @@ def load_module(

module = importlib.util.module_from_spec(spec)
__MODULES__[fqdn] = module
__MODULE_SPECS__[module] = spec
cdfs = {}
for name, metadata in gather_class_definitions_from_module_path(
path, None
Expand All @@ -226,14 +234,11 @@ def object_belongs_to_sure(object: object) -> bool:
:param object: an :class:`object` object
:returns: ``True`` if the given ``object`` passes this function heuristics to verify that the object belongs to :mod:`sure`
"""
module_name = (
getattr(
object,
"__module__",
getattr(getattr(object, "__class__", object), "__module__", ""),
)
or ""
)
module_name = getattr(
object,
"__module__",
getattr(getattr(object, "__class__", object), "__module__", ""),
) or ""
heuristics = [
lambda: module_name == "sure",
lambda: module_name.startswith("sure."),
Expand Down
9 changes: 8 additions & 1 deletion sure/loader/astutil.py
Expand Up @@ -19,6 +19,7 @@

from typing import Dict, List, Optional, Tuple, Union
from pathlib import Path
from sure.errors import send_runtime_warning


def is_classdef(node: ast.stmt) -> bool:
Expand Down Expand Up @@ -75,7 +76,13 @@ def gather_class_definitions_from_module_path(
class is defined and a tuple with the names of its base classes.
"""

with Path(path).open() as f:
path = Path(path)

if path.is_symlink() and not path.resolve().exists(): # avoid loading broken symlinks
send_runtime_warning(f"parsing skipped of irregular file `{path.absolute()}'")
return {}

with path.open() as f:
node = ast.parse(f.read())

return gather_class_definitions_node(node, {}, nearest_line=nearest_line)
1 change: 0 additions & 1 deletion sure/original.py
Expand Up @@ -31,7 +31,6 @@
from typing import Union
from collections.abc import Iterable


from sure.core import Explanation
from sure.core import DeepComparison
from sure.core import itemize_length
Expand Down
2 changes: 1 addition & 1 deletion sure/version.py
@@ -1 +1 @@
version = "3.0a1"
version = "3.0a0"

0 comments on commit 9bfded6

Please sign in to comment.