Skip to content

Commit

Permalink
more test coverage]
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielfalcao committed Jan 21, 2024
1 parent 6843aa4 commit 28be05d
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 117 deletions.
2 changes: 0 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,11 @@ docs: html-docs
$(OPEN_COMMAND) docs/build/html/index.html

test tests:
@$(VENV)/bin/pytest --cov=sure tests/unit/test_astuneval.py
@$(VENV)/bin/pytest --cov=sure tests

# runs main command-line tool
run: | $(LIBEXEC_PATH)
$(LIBEXEC_PATH) --reap-warnings tests/crashes
$(LIBEXEC_PATH) --reap-warnings --special-syntax --with-coverage --cover-branches --cover-erase --cover-module=sure.core --cover-module=sure tests/runner
$(LIBEXEC_PATH) --reap-warnings --special-syntax --with-coverage --cover-branches --cover-erase --cover-module=sure --immediate --cover-module=sure --ignore tests/crashes tests

push-release: dist # pushes distribution tarballs of the current version
Expand Down
5 changes: 4 additions & 1 deletion docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ v3.0.0
- :meth:`sure.original.AssertionHelper.differs`
- :meth:`sure.original.AssertionHelper.has`
- :meth:`sure.original.AssertionHelper.is_a`

- :meth:`sure.original.AssertionHelper.every_item_is`
- :meth:`sure.original.AssertionHelper.at`
- :meth:`sure.original.AssertionHelper.like`
- Feel free to open an issue requesting any of those methods to be added back to Sure's codebase.

[v2.0.0]
--------
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tool:pytest]
addopts = --cov=sure --ignore tests/crashes -v --capture=no --disable-warnings --maxfail=1
addopts = --cov=sure --ignore tests/crashes -v --capture=no --disable-warnings
testpaths =
tests
filterwarnings =
Expand Down
54 changes: 3 additions & 51 deletions sure/original.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def __init__(self, src,
if all_integers(within_range):
if len(within_range) != 2:
raise TypeError(
'within_range parameter must be a tuple with 2 objects',
f"within_range parameter must be a tuple with 2 objects, received a `{type(within_range).__name__}' with {len(within_range)} objects instead",
)

self._range = within_range
Expand Down Expand Up @@ -115,7 +115,7 @@ def match(self, *args, **kw):

def raises(self, exc, msg=None):
if not callable(self.actual):
raise TypeError(f'{self.actual} is not callable')
raise TypeError(f'{repr(self.actual)} is not callable')

try:
self.actual(*self._callable_args, **self._callable_kw)
Expand Down Expand Up @@ -147,18 +147,6 @@ def raises(self, exc, msg=None):
f'Expected to match regex: {repr(msg.pattern)}\n against:\n {repr(str(err))}'
)

elif isinstance(msg, (str, )) and msg not in str(err):
raise AssertionError(
'When calling %r the exception message does not match. ' \
'Expected: %r\n got:\n %r' % (self.actual, msg, err)
)

elif isinstance(msg, re.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_caller_location(self.actual), msg.pattern, err)
)

else:
raise e
else:
Expand All @@ -177,12 +165,7 @@ def raises(self, exc, msg=None):
self._callable_kw, exc))
else:
raise AssertionError(
'at %s:\ncalling %s() with args %r and kws %r did not raise %r' % (
_src_filename,
self.actual.__name__,
self._callable_args,
self._callable_kw, exc
)
f'at {_src_filename}:\ncalling {self.actual.__name__}() with args {repr(self._callable_args)} and kws {repr(self._callable_kw)} did not raise {repr(exc)}'
)

return True
Expand Down Expand Up @@ -215,7 +198,6 @@ def equals(self, expectation):
return True

def looks_like(self, expectation):
comp = DeepComparison(self.actual, expectation)
old_src = pformat(self.actual)
old_dst = pformat(expectation)
self.actual = re.sub(r'\s', '', self.actual).lower()
Expand All @@ -226,29 +208,6 @@ def looks_like(self, expectation):
else:
raise AssertionError(error)

def every_item_is(self, expectation):
msg = 'all members of %r should be %r, but the %dth is %r'
for index, item in enumerate(self.actual):
if self._range:
if index < self._range[0] or index > self._range[1]:
continue

error = msg % (self.actual, expectation, index, item)
if item != expectation:
raise AssertionError(error)

return True

def at(self, key):
if not self.has(key):
raise AssertionError(f"key {key} not present in {self.actual}")

if isinstance(self.actual, dict):
return AssertionHelper(self.actual[key])

else:
return AssertionHelper(getattr(self.actual, key))

def _get_int_or_length(self, obj: Union[int, typing.Iterable]):
if isinstance(obj, Iterable):
return len(obj)
Expand Down Expand Up @@ -344,9 +303,6 @@ def len_is_not(self, that: Union[int, typing.Iterable]):

return True

def like(self, that):
return self.has(that)

def the_attribute(self, attr):
self._attribute = attr
return self
Expand Down Expand Up @@ -375,10 +331,6 @@ def matches(self, items):
)

for index, (item, other) in enumerate(zip(self.actual, items)):
if self._range:
if index < self._range[0] or index > self._range[1]:
continue

value = get_eval(item)

error = msg % (self.actual, index, self.__element_access_expr__, other, value)
Expand Down
105 changes: 57 additions & 48 deletions tests/test_original_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,19 @@
import os
import sure
import time

from datetime import datetime

from sure import that, this
from sure import expects
from sure import action_for
from sure import scenario
from sure import within
from sure import second, miliseconds
from sure import StagingArea
from sure.doubles import Dummy, anything
from sure.errors import WrongUsageError
from sure.special import is_cpython
from sure.loader import collapse_path
from sure.original import all_integers
from sure.original import all_integers, AssertionHelper


def test_setup_with_context():
Expand Down Expand Up @@ -63,7 +62,7 @@ def it_crashes():
assert that(it_crashes).raises(
TypeError,
(
"the function it_crashes defined at tests/test_original_api.py line 60, is being "
"the function it_crashes defined at tests/test_original_api.py line 59, is being "
"decorated by either @that_with_context or @scenario, so it should "
"take at least 1 parameter, which is the test context"
),
Expand Down Expand Up @@ -267,28 +266,11 @@ def __repr__(self):
assert that(shapes, within_range=(1, 2)).the_attribute("name").equals("square")


def test_that_checking_all_elements():
"that(iterable).every_item_is('value')"
shapes = [
"cube",
"ball",
"ball",
"piramid",
]

assert shapes[0] != "ball"
assert shapes[3] != "ball"

assert shapes[1] == "ball"
assert shapes[2] == "ball"

assert that(shapes, within_range=(1, 2)).every_item_is("ball")


def test_that_checking_each_matches():
"that(iterable).in_each('').equals('value')"

class animal(object):

def __init__(self, kind):
self.attributes = {
"class": "mammal",
Expand All @@ -305,7 +287,6 @@ def __init__(self, kind):

assert animals[0].attributes["kind"] != "cow"
assert animals[1].attributes["kind"] != "cow"

assert animals[2].attributes["kind"] == "cow"
assert animals[3].attributes["kind"] == "cow"
assert animals[4].attributes["kind"] == "cow"
Expand All @@ -329,17 +310,9 @@ def __init__(self, kind):
.matches(["dog", "cat", "cow", "cow", "cow"])
)

try:
assert that(animals).in_each("attributes['kind']").matches(["dog"])
assert False, "should not reach here"
except AssertionError as e:
assert that(str(e)).equals(
"%r has 5 items, but the matching list has 1: %r"
% (
["dog", "cat", "cow", "cow", "cow"],
["dog"],
)
)
expects(that(animals).in_each("attributes['kind']").matches).when.called_with(["dog"]).should.have.raised(
f"{repr(['dog', 'cat', 'cow', 'cow', 'cow'])} has 5 items, but the matching list has 1: {repr(['dog'])}"
)


def test_that_raises():
Expand Down Expand Up @@ -587,20 +560,12 @@ def test_within_pass():

def test_within_five_milicesonds_fails_when_function_takes_six_miliseconds():
"within(five=miliseconds) should fail when the decorated function takes six miliseconds to run"

def sleepy(*a):
time.sleep(0.6)

failed = False
try:
within(five=miliseconds)(sleepy)()
except AssertionError as e:
failed = True
expects(
"sleepy [tests/test_original_api.py line 591] did not run within five miliseconds"
).to.equal(str(e))

assert failed, "within(five=miliseconds)(sleepy) did not fail"
expects(within(five=miliseconds)(sleepy)).when.called.to.have.raised(
"sleepy [tests/test_original_api.py line 563] did not run within five miliseconds"
)


def test_that_is_a_matcher_should_absorb_callables_to_be_used_as_matcher():
Expand Down Expand Up @@ -767,7 +732,7 @@ def test_depends_on_failing_due_to_lack_of_attribute_in_context():

fullpath = collapse_path(os.path.abspath(__file__))
error = (
f'the action "variant_action" defined at {fullpath}:776 '
f'the action "variant_action" defined at {fullpath}:741 '
'depends on the attribute "data_structure" to be available in the'
" current context"
)
Expand All @@ -789,11 +754,12 @@ def test_depends_on_failing_due_not_calling_a_previous_action():
"it fails when an action depends on some attribute that is being " "provided by other actions"

fullpath = collapse_path(os.path.abspath(__file__))

error = (
'the action "my_action" defined at {0}:804 '
'the action "my_action" defined at {0}:770 '
'depends on the attribute "some_attr" to be available in the context.'
" Perhaps one of the following actions might provide that attribute:\n"
" -> dependency_action at {0}:800".replace("{0}", fullpath)
" -> dependency_action at {0}:766".replace("{0}", fullpath)
)

def with_setup(context):
Expand Down Expand Up @@ -1635,3 +1601,46 @@ def test_within_wrong_usage():
WrongUsageError,
"within() takes a single keyword argument where the argument must be a numerical description from one to eighteen and the value. For example: within(eighteen=miliseconds)",
)


def test_assertion_helper_within_range_wrong_number_of_elements():
expects(AssertionHelper).when.called_with(object, within_range=set(range(3))).should.have.raised(
TypeError,
"within_range parameter must be a tuple with 2 objects, received a `set' with 3 objects instead"
)


def test_assertion_helper_with_kws():
src = Dummy('assertion_helper.src')
assertion_helper = AssertionHelper(src, with_args=("z", "y"), with_kws={"a": "b"})
expects(assertion_helper).to.have.property("_callable_args").being.a(list)
expects(assertion_helper).to.have.property("_callable_args").being.equal(["z", "y"])
expects(assertion_helper).to.have.property("_callable_kw").being.a(dict)
expects(assertion_helper).to.have.property("_callable_kw").being.equal({"a": "b"})
expects(assertion_helper).to.have.property("src").being.a(Dummy)
expects(assertion_helper).to.have.property("src").being.equal(src)


def test_assertion_helper_raises_raises_type_error_noncallable():
src = Dummy('assertion_helper.src')
assertion_helper = AssertionHelper(src)
expects(assertion_helper.raises).when.called_with("dummy").to.have.raised(
TypeError,
"<Dummy assertion_helper.src> is not callable"
)


def test_assertion_helper_raises_fails_when_the_expected_error_does_not_happen_given_function():
assertion_helper = AssertionHelper(lambda: None)

expects(assertion_helper.raises).when.called_with("error").to.have.raised(
f'calling function <lambda>({collapse_path(__file__)} at line: "1634") with args [] and kws {{}} did not raise {repr("error")}'
)


def test_assertion_helper_raises_fails_when_the_expected_error_does_not_happen_builtin_function():
assertion_helper = AssertionHelper(vars)

expects(assertion_helper.raises).when.called_with("error").to.have.raised(
"at <built-in function>:\ncalling vars() with args [] and kws {} did not raise 'error'"
)
44 changes: 30 additions & 14 deletions tests/unit/test_astuneval.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,35 @@ class MonacoGrandPrix1990:


def test_parse_accessor_attr_accessor():
class FirstResponder:
def __init__(self, bound: str, damage: str):
self.bound = bound
self.damage = damage

class Incident:
first_responders = [
FirstResponder("Wyckoff", "unknown"),
FirstResponder("Beth Israel", "unknown"),
FirstResponder("Brooklyn Hospital Center", "unknown"),
FirstResponder("Woodhull", "administered wrong medication"),
class Event:
def __init__(self, description: str):
self.tag = description

class LogBook:
events = [
Event("occurrenceA"),
Event("occurrenceB"),
Event("occurrenceC"),
Event("occurrenceD"),
Event("occurrenceE"),
Event("occurrenceF"),
]
expects(parse_accessor("first_responders[3].damage")).to.be.a(AttributeAccessor)

access_damage = parse_accessor("first_responders[3].damage")
expects(access_damage(Incident)).to.equal("administered wrong medication")
expects(parse_accessor("events[3].description")).to.be.a(AttributeAccessor)

access_description = parse_accessor("events[3].tag")
expects(access_description(LogBook)).to.equal("occurrenceD")


def test_accessor_access_not_implemented():
accessor = Accessor(parse_body("attribute"))
expects(accessor.access).when.called_with(object).to.throw(
NotImplementedError
)


def test_parse_body_syntax_error():
parse_body.when.called_with("substance = collect()\nsubstance.reuse()").to.throw(
SyntaxError,
"'substance = collect()\\nsubstance.reuse()' exceeds the maximum body count for ast nodes"
)

0 comments on commit 28be05d

Please sign in to comment.