From 9bfded6231e7520f087b52f269481d75e5cfd197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Falc=C3=A3o?= Date: Fri, 19 Jan 2024 02:17:06 +0000 Subject: [PATCH] presents more test coverage --- docs/source/api-reference.rst | 3 + docs/source/assertion-reference.rst | 11 + docs/source/changelog.rst | 2 +- sure/__init__.py | 108 ++++---- sure/cli.py | 5 +- sure/loader/__init__.py | 35 +-- sure/loader/astutil.py | 9 +- sure/original.py | 1 - sure/version.py | 2 +- tests/functional/loader/test_loader.py | 20 +- tests/test_assertion_builder.py | 110 ++++++-- ...est_assertion_builder_assertion_methods.py | 29 +++ ..._assertion_builder_assertion_properties.py | 32 +++ tests/test_loader_astutil.py | 19 +- tests/test_original_api.py | 240 +++++++++--------- 15 files changed, 404 insertions(+), 222 deletions(-) create mode 100644 tests/test_assertion_builder_assertion_methods.py create mode 100644 tests/test_assertion_builder_assertion_properties.py diff --git a/docs/source/api-reference.rst b/docs/source/api-reference.rst index 69b8b07..64d8969 100644 --- a/docs/source/api-reference.rst +++ b/docs/source/api-reference.rst @@ -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`` ---------------------- diff --git a/docs/source/assertion-reference.rst b/docs/source/assertion-reference.rst index c4e22de..ebf04c6 100644 --- a/docs/source/assertion-reference.rst +++ b/docs/source/assertion-reference.rst @@ -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 ------------------ diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 0ca70a5..cc966ef 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -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 `__ diff --git a/sure/__init__.py b/sure/__init__.py index f744611..51af5fd 100644 --- a/sure/__init__.py +++ b/sure/__init__.py @@ -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 @@ -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` @@ -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 @@ -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 @@ -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): @@ -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 @@ -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 " @@ -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 diff --git a/sure/cli.py b/sure/cli.py index 370f0e9..995e325 100644 --- a/sure/cli.py +++ b/sure/cli.py @@ -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() @@ -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: diff --git a/sure/loader/__init__.py b/sure/loader/__init__.py index 5edaabc..2480e15 100644 --- a/sure/loader/__init__.py +++ b/sure/loader/__init__.py @@ -14,7 +14,6 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - import os import sys import ast @@ -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__ = {} @@ -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 @@ -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, @@ -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__) @@ -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): @@ -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] @@ -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 @@ -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."), diff --git a/sure/loader/astutil.py b/sure/loader/astutil.py index 49ba007..a295fb7 100644 --- a/sure/loader/astutil.py +++ b/sure/loader/astutil.py @@ -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: @@ -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) diff --git a/sure/original.py b/sure/original.py index eedfcbb..66babe8 100644 --- a/sure/original.py +++ b/sure/original.py @@ -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 diff --git a/sure/version.py b/sure/version.py index 3477736..9f8ea32 100644 --- a/sure/version.py +++ b/sure/version.py @@ -1 +1 @@ -version = "3.0a1" +version = "3.0a0" diff --git a/tests/functional/loader/test_loader.py b/tests/functional/loader/test_loader.py index 8c5ab03..074dead 100644 --- a/tests/functional/loader/test_loader.py +++ b/tests/functional/loader/test_loader.py @@ -29,7 +29,7 @@ loader, ) -fake_packages_path = Path(__file__).parent.joinpath("fake_packages") +fake_packages_path = Path(__file__).parent.joinpath("fake_packages").absolute() def test_get_package_upmost__init__containing(): @@ -178,6 +178,24 @@ def test_loader_load_python_path_returns_empty_list_when_given_path_is_a_directo send_runtime_warning.assert_called_once_with(f"ignoring {fake_packages_path} for being a directory") +@patch('sure.loader.send_runtime_warning') +@patch('sure.loader.Path') +def test_loader_load_python_path_returns_empty_list_when_given_path_is_a_broken_symlink(Path, send_runtime_warning): + "sure.loader.loader.load_python_path() should return empty list when receiving a :class:`pathlib.Path` that points to a broken symlink" + + path = Path.return_value + path.is_dir.return_value = False + path.is_symlink.return_value = True + path.resolve.return_value.exists.return_value = False + path.absolute.return_value = "absolute-path-dummy" + modules = loader.load_python_path(fake_packages_path) + + expects(modules).to.be.a(list) + expects(modules).to.be.empty + + send_runtime_warning.assert_called_once_with("parsing skipped of irregular file `absolute-path-dummy'") + + @patch('sure.loader.send_runtime_warning') def test_loader_load_python_path_returns_empty_list_when_given_path_seems_to_be_a_dunder_file(send_runtime_warning): "sure.loader.loader.load_python_path() should return empty list when receiving a :class:`pathlib.Path` that is a directory" diff --git a/tests/test_assertion_builder.py b/tests/test_assertion_builder.py index 90842b8..26105c2 100644 --- a/tests/test_assertion_builder.py +++ b/tests/test_assertion_builder.py @@ -35,7 +35,7 @@ def test_4_equal_2p2(): - "this(4).should.equal(2 + 2)" + "expects(4).should.equal(2 + 2)" time = datetime.now() - timedelta(0, 60) @@ -57,7 +57,7 @@ def incorrect_negative_expectation(): def test_2_within_0a2(): - "this(1).should.be.within(0, 2)" + "expects(1).should.be.within(0, 2)" expect(1).should.be.within(0, 2) expect(4).should_not.be.within(0, 2) @@ -76,7 +76,7 @@ def opposite_not(): def test_true_to_be_ok(): - "this(True).should.be.ok" + "expects(True).should.be.ok" expect(True).should.be.ok expect(False).should_not.be.ok @@ -95,7 +95,7 @@ def opposite_not(): def test_falsy(): - "this(False).should.be.false" + "expects(False).should.be.false" expect(False).should.be.falsy expect(True).should_not.be.falsy @@ -114,7 +114,7 @@ def opposite_not(): def test_none(): - "this(None).should.be.none" + "expects(None).should.be.none" expect(None).should.be.none expect(not None).should_not.be.none @@ -133,7 +133,7 @@ def opposite_not(): def test_should_be_a(): - "this(None).should.be.none" + "expects(None).should.be.none" expect(1).should.be.an(int) expect([]).should.be.a('collections.abc.Iterable') @@ -153,7 +153,7 @@ def opposite_not(): def test_should_be_callable(): - "this(function).should.be.callable" + "expects(function).should.be.callable" expect(lambda: None).should.be.callable expect("aa").should_not.be.callable @@ -175,7 +175,7 @@ def opposite_not(): def test_iterable_should_be_empty(): - "this(iterable).should.be.empty" + "expects(iterable).should.be.empty" expect([]).should.be.empty expect([1, 2, 3]).should_not.be.empty @@ -196,7 +196,7 @@ def opposite_not(): def test_iterable_should_have_length_of(): - "this(iterable).should.have.length_of(N)" + "expects(iterable).should.have.length_of(N)" expect({'foo': 'bar', 'a': 'b'}).should.have.length_of(2) expect([1, 2, 3]).should_not.have.length_of(4) @@ -218,7 +218,7 @@ def opposite_not(): def test_greater_than(): - "this(X).should.be.greater_than(Y)" + "expects(X).should.be.greater_than(Y)" expect(5).should.be.greater_than(4) expect(1).should_not.be.greater_than(2) @@ -239,7 +239,7 @@ def opposite_not(): def test_greater_than_or_equal_to(): - "this(X).should.be.greater_than_or_equal_to(Y)" + "expects(X).should.be.greater_than_or_equal_to(Y)" expect(4).should.be.greater_than_or_equal_to(4) expect(1).should_not.be.greater_than_or_equal_to(2) @@ -260,7 +260,7 @@ def opposite_not(): def test_lower_than(): - "this(X).should.be.lower_than(Y)" + "expects(X).should.be.lower_than(Y)" expect(4).should.be.lower_than(5) expect(2).should_not.be.lower_than(1) @@ -281,7 +281,7 @@ def opposite_not(): def test_lower_than_or_equal_to(): - "this(X).should.be.lower_than_or_equal_to(Y)" + "expects(X).should.be.lower_than_or_equal_to(Y)" expect(5).should.be.lower_than_or_equal_to(5) expect(2).should_not.be.lower_than_or_equal_to(1) @@ -302,21 +302,21 @@ def opposite_not(): def test_assertion_builder_be__call__(): - "this(ACTUAL).should.be(EXPECTED) where ACTUAL and EXPECTED are evaluated as identical in Python" + "expects(ACTUAL).should.be(EXPECTED) where ACTUAL and EXPECTED are evaluated as identical in Python" d1 = {} d2 = d1 d3 = {} - assert isinstance(this(d2).should.be(d1), bool) + assert isinstance(expects(d2).should.be(d1), bool) expect(d2).should.be(d1) expect(d3).should_not.be(d1) def wrong_should(): - return this(d3).should.be(d1) + return expects(d3).should.be(d1) def wrong_should_not(): - return this(d2).should_not.be(d1) + return expects(d2).should_not.be(d1) expect(wrong_should_not).when.called.should.throw( AssertionError, @@ -329,7 +329,7 @@ def wrong_should_not(): def test_have_property(): - "this(instance).should.have.property(property_name)" + "expects(instance).should.have.property(property_name)" class ChemicalElement(object): name = "Uranium" @@ -360,7 +360,7 @@ def opposite_not(): def test_have_property_with_value(): - ("this(instance).should.have.property(property_name).being or " + ("expects(instance).should.have.property(property_name).being or " ".with_value should allow chain up") class ChemicalElement(object): @@ -394,7 +394,7 @@ def opposite_not(): def test_have_key(): - "this(dictionary).should.have.key(key_data)" + "expects(dictionary).should.have.key(key_data)" data_structure = {'data': "binary blob"} @@ -420,7 +420,7 @@ def opposite_not(): def test_have_key_with_value(): - ("this(dictionary).should.have.key(key_name).being or " + ("expects(dictionary).should.have.key(key_name).being or " ".with_value should allow chain up") chemical_element = dict(name="Uranium") @@ -450,7 +450,7 @@ def opposite_not(): def test_look_like(): - "this(' aa \n ').should.look_like('aa')" + "expects(' aa \n ').should.look_like('aa')" expect(' \n aa \n ').should.look_like('AA') expect(' \n bb \n ').should_not.look_like('aa') @@ -861,3 +861,69 @@ def trigger(): AssertionError, "test [tests/test_assertion_builder.py line 853] did not run within one microseconds" ) + + +def test_assertion_builder_with_args(): + "AssertionBuilder() should accept `with_args' option" + + assertion_builder = AssertionBuilder("ab", with_args=['a', 'b']) + assert isinstance(getattr(assertion_builder, "_callable_args", None), list) + assert getattr(assertion_builder, "_callable_args", None) == ["a", "b"] + + assertion_builder = AssertionBuilder("ab", with_args=('a', 'b')) + assert isinstance(getattr(assertion_builder, "_callable_args", None), list) + assert getattr(assertion_builder, "_callable_args", None) == ["a", "b"] + + +def test_assertion_builder_with_kws(): + "AssertionBuilder() should accept `with_kws' option" + + assertion_builder = AssertionBuilder("test", with_kws={"foo": "bar"}) + assert isinstance(getattr(assertion_builder, "_callable_kw", None), dict) + assert getattr(assertion_builder, "_callable_kw", None) == {"foo": "bar"} + + assertion_builder = AssertionBuilder("test", with_kws={"foo": "bar"}) + assert isinstance(getattr(assertion_builder, "_callable_kw", None), dict) + assert getattr(assertion_builder, "_callable_kw", None) == {"foo": "bar"} + + +def test_assertion_builder_and_kws(): + "AssertionBuilder() should accept `and_kws' option" + + assertion_builder = AssertionBuilder("test", and_kws={"foo": "bar"}) + assert isinstance(getattr(assertion_builder, "_callable_kw", None), dict) + assert getattr(assertion_builder, "_callable_kw", None) == {"foo": "bar"} + + assertion_builder = AssertionBuilder("test", and_kws={"foo": "bar"}) + assert isinstance(getattr(assertion_builder, "_callable_kw", None), dict) + assert getattr(assertion_builder, "_callable_kw", None) == {"foo": "bar"} + + +def test_list_should_equal_list_empty_or_different_size(): + "expects(iterableA).to.equal(iterableZ)" + + expects([1, 2, 3]).to.equal([1, 2, 3]) + + def compare_with_empty_list(): + expects([1, 2, 3]).to.equal([]) + + def compare_with_shorter_length(): + expects([1, 2, 3]).to.equal([1, 2]) + + def compare_with_longer_length(): + expects([1, 2, 3]).to.equal([1, 2, 4, 6]) + + expect(compare_with_empty_list).when.called.to.throw(AssertionError) + expect(compare_with_empty_list).when.called.to.throw( + "X has 3 items whereas Y is empty" + ) + + expect(compare_with_shorter_length).when.called.to.throw(AssertionError) + expect(compare_with_shorter_length).when.called.to.throw( + "X has 3 items whereas Y has only 2" + ) + + expect(compare_with_longer_length).when.called.to.throw(AssertionError) + expect(compare_with_longer_length).when.called.to.throw( + "Y has 4 items whereas X has only 3" + ) diff --git a/tests/test_assertion_builder_assertion_methods.py b/tests/test_assertion_builder_assertion_methods.py new file mode 100644 index 0000000..0c0e05b --- /dev/null +++ b/tests/test_assertion_builder_assertion_methods.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) <2010-2024> 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 . +"""tests for :class:`sure.AssertionBuilder` properties defined with the +decorator :func:`sure.assertionmethod`""" + +from sure import expects +from sure.doubles import anything_of_type + + +def test_contains(): + "expects.that().contains" + + expects.that(set(range(8))).contains(7) + expects(set(range(13))).to.contain(8) + expects(set(range(33))).to_contain(3) diff --git a/tests/test_assertion_builder_assertion_properties.py b/tests/test_assertion_builder_assertion_properties.py new file mode 100644 index 0000000..6a80110 --- /dev/null +++ b/tests/test_assertion_builder_assertion_properties.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) <2010-2024> 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 . +"""tests for :class:`sure.AssertionBuilder` properties defined with the +decorator :func:`sure.assertionproperty`""" + +from sure import expects +from sure.doubles import anything_of_type + + +def test_not_have(): + "expects().to.not_have" + + class WaveFunctionParameters: + period = anything_of_type(float) + amplitude = anything_of_type(float) + frequency = anything_of_type(float) + + expects(WaveFunctionParameters).to.not_have.property("unrequested_phase_change") diff --git a/tests/test_loader_astutil.py b/tests/test_loader_astutil.py index 26df8b7..b8a59d8 100644 --- a/tests/test_loader_astutil.py +++ b/tests/test_loader_astutil.py @@ -16,6 +16,7 @@ # along with this program. If not, see . import unittest from unittest import TestCase +from unittest.mock import patch from sure import expects from sure.loader.astutil import gather_class_definitions_from_module_path, gather_class_definitions_node @@ -24,7 +25,7 @@ class TestLoaderAstUtilBaseClassName(TestCase): def test_gather_class_definitions_from_module_path(self): classes = gather_class_definitions_from_module_path(__file__) expects(classes).to.equal( - {'TestLoaderAstUtilBaseClassName': (23, ('TestCase',)), 'TestLoaderAstUtilBaseClassAttributeAndName': (31, ('unittest.TestCase',))} + {'TestLoaderAstUtilBaseClassName': (24, ('TestCase',)), 'TestLoaderAstUtilBaseClassAttributeAndName': (32, ('unittest.TestCase',))} ) @@ -32,7 +33,7 @@ class TestLoaderAstUtilBaseClassAttributeAndName(unittest.TestCase): def test_gather_class_definitions_from_module_path(self): classes = gather_class_definitions_from_module_path(__file__) expects(classes).to.equal( - {'TestLoaderAstUtilBaseClassName': (23, ('TestCase',)), 'TestLoaderAstUtilBaseClassAttributeAndName': (31, ('unittest.TestCase',))} + {'TestLoaderAstUtilBaseClassName': (24, ('TestCase',)), 'TestLoaderAstUtilBaseClassAttributeAndName': (32, ('unittest.TestCase',))} ) @@ -40,3 +41,17 @@ def test_gather_class_definitions_node_with_string(): "sure.laoder.astutil.gather_class_definitions_node() with a string" expects(gather_class_definitions_node("string", classes={})).to.equal({}) + + +@patch('sure.loader.astutil.send_runtime_warning') +@patch('sure.loader.astutil.Path') +def test_gather_class_definitions_from_module_path_symlink(Path, send_runtime_warning): + "sure.laoder.astutil.gather_class_definitions_from_module_path() with a symlink" + + path = Path.return_value + path.is_symlink.return_value = True + path.resolve.return_value.exists.return_value = False + path.absolute.return_value = "absolute-path-dummy" + expects(gather_class_definitions_from_module_path("path")).to.equal({}) + Path.assert_called_once_with("path") + send_runtime_warning.assert_called_once_with("parsing skipped of irregular file `absolute-path-dummy'") diff --git a/tests/test_original_api.py b/tests/test_original_api.py index 5c679a3..177c2d1 100644 --- a/tests/test_original_api.py +++ b/tests/test_original_api.py @@ -457,9 +457,7 @@ def function(arg1=None, arg2=None): assert called called = False - assert that(function, with_args=[1], and_kws={"arg2": 2}).raises( - "yeah, it failed" - ) + assert that(function, with_args=[1], and_kws={"arg2": 2}).raises("yeah, it failed") assert called called = False @@ -650,9 +648,7 @@ def __call__(self): range_name = range.__name__ assert that(fail_1).raises("X is a list and Y is a {0} instead".format(range_name)) assert that(Fail2).raises("X is a {0} and Y is a list instead".format(range_name)) - assert that(Fail3()).raises( - "X is a {0} and Y is a list instead".format(range_name) - ) + assert that(Fail3()).raises("X is a {0} and Y is a list instead".format(range_name)) def test_within_pass(): @@ -672,105 +668,13 @@ def sleepy(*a): within(five=miliseconds)(sleepy)() except AssertionError as e: failed = True - expects("sleepy [tests/test_original_api.py line 667] did not run within five miliseconds").to.equal(str(e)) + expects( + "sleepy [tests/test_original_api.py line 663] did not run within five miliseconds" + ).to.equal(str(e)) assert failed, "within(five=miliseconds)(sleepy) did not fail" -def test_word_to_number(): - expects(sure.word_to_number("one")).to.equal(1) - expects(sure.word_to_number("two")).to.equal(2) - expects(sure.word_to_number("three")).to.equal(3) - expects(sure.word_to_number("four")).to.equal(4) - expects(sure.word_to_number("five")).to.equal(5) - expects(sure.word_to_number("six")).to.equal(6) - expects(sure.word_to_number("seven")).to.equal(7) - expects(sure.word_to_number("eight")).to.equal(8) - expects(sure.word_to_number("nine")).to.equal(9) - expects(sure.word_to_number("ten")).to.equal(10) - expects(sure.word_to_number("eleven")).to.equal(11) - expects(sure.word_to_number("twelve")).to.equal(12) - expects(sure.word_to_number("thirteen")).to.equal(13) - expects(sure.word_to_number("fourteen")).to.equal(14) - expects(sure.word_to_number("fifteen")).to.equal(15) - expects(sure.word_to_number("sixteen")).to.equal(16) - - -def test_word_to_number_fail(): - failed = False - try: - sure.word_to_number("twenty") - except AssertionError as e: - failed = True - expects(str(e)).to.equal( - "sure supports only literal numbers from one " - 'to sixteen, you tried the word "twenty"' - ) - - assert failed, "should raise assertion error" - - -def test_microsecond_unit(): - "testing microseconds convertion" - cfrom, cto = sure.UNITS[sure.microsecond] - - expects(cfrom(1)).to.equal(100000) - expects(cto(1)).to.equal(1) - - cfrom, cto = sure.UNITS[sure.microseconds] - - expects(cfrom(1)).to.equal(100000) - expects(cto(1)).to.equal(1) - - -def test_milisecond_unit(): - "testing miliseconds convertion" - cfrom, cto = sure.UNITS[sure.milisecond] - - expects(cfrom(1)).to.equal(1000) - expects(cto(100)).to.equal(1) - - cfrom, cto = sure.UNITS[sure.miliseconds] - - expects(cfrom(1)).to.equal(1000) - expects(cto(100)).to.equal(1) - - -def test_second_unit(): - "testing seconds convertion" - cfrom, cto = sure.UNITS[sure.second] - - expects(cfrom(1)).to.equal(1) - expects(cto(100000)).to.equal(1) - - cfrom, cto = sure.UNITS[sure.seconds] - - expects(cfrom(1)).to.equal(1) - expects(cto(100000)).to.equal(1) - - -def test_minute_unit(): - "testing minutes convertion" - cfrom, cto = sure.UNITS[sure.minute] - - expects(cfrom(60)).to.equal(1) - expects(cto(1)).to.equal(6000000) - - cfrom, cto = sure.UNITS[sure.minutes] - - expects(cfrom(60)).to.equal(1) - expects(cto(1)).to.equal(6000000) - - -def test_within_wrong_usage(): - "within(three=miliseconds, one=second) should raise WrongUsageError" - - expects(within).when.called_with(three=miliseconds, one=second).to.have.raised( - 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_that_is_a_matcher_should_absorb_callables_to_be_used_as_matcher(): "that.is_a_matcher should absorb callables to be used as matcher" @@ -910,9 +814,9 @@ def test_fails_when_action_doesnt_fulfill_the_agreement_of_its_provides_argument error = ( 'the action "unreasonable_action" is supposed to provide the ' 'attribute "two" into the context but does not. ' - 'Check its implementation for correctness or, if ' - 'there is a bug in Sure, consider reporting that at ' - 'https://github.com/gabrielfalcao/sure/issues' + "Check its implementation for correctness or, if " + "there is a bug in Sure, consider reporting that at " + "https://github.com/gabrielfalcao/sure/issues" ) def with_setup(context): @@ -923,9 +827,11 @@ def unreasonable_action(): @scenario(with_setup) def reasoning_of_an_unreasonable_action(context): expects(context.unreasonable_action).to.have.raised(AssertionError, error) - return 'relativist' + return "relativist" - expects(reasoning_of_an_unreasonable_action).when.called.to.return_value('relativist') + expects(reasoning_of_an_unreasonable_action).when.called.to.return_value( + "relativist" + ) def test_depends_on_failing_due_to_lack_of_attribute_in_context(): @@ -933,7 +839,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}:942 ' + f'the action "variant_action" defined at {fullpath}:848 ' 'depends on the attribute "data_structure" to be available in the' " current context" ) @@ -956,10 +862,10 @@ def test_depends_on_failing_due_not_calling_a_previous_action(): fullpath = collapse_path(os.path.abspath(__file__)) error = ( - 'the action "my_action" defined at {0}:970 ' + 'the action "my_action" defined at {0}:876 ' '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}:966".replace("{0}", fullpath) + " -> dependency_action at {0}:872".replace("{0}", fullpath) ) def with_setup(context): @@ -1035,7 +941,7 @@ def access_nonexisting_attribute(): ) -def test_actions_providing_dinamically_named_variables(): +def test_actions_providing_dynamically_named_variables(): "the actions should be able to declare the variables they provide" def with_setup(context): @@ -1156,7 +1062,7 @@ def assertions(): "X = ['one', 'yeah']\n" " and\n" "Y = ['one', 'yeah', 'damn']\n" - "Y has 3 items whereas X has only 2" + "Y has 3 items whereas X has only 2", ) @@ -1179,7 +1085,7 @@ def assertions(): "X = {'three': 'value'}\n" " and\n" "Y = {'two': 'value'}\n" - "X has the key \"'three'\" whereas Y does not" + "X has the key \"'three'\" whereas Y does not", ) @@ -1459,7 +1365,7 @@ def assertions(): "X = {'index': [{'age': 33, 'name': 'JC'}]}\n" " and\n" "Y = {'index': [{'age': 31, 'foo': 'bar', 'name': 'JC'}]}\n" - "X['index'][0] does not have the key \"'foo'\" whereas Y['index'][0] has it" + "X['index'][0] does not have the key \"'foo'\" whereas Y['index'][0] has it", ) @@ -1486,7 +1392,7 @@ def assertions(): "X = {'index': [{'age': 33, 'foo': 'bar', 'name': 'JC'}]}\n" " and\n" "Y = {'index': [{'age': 31, 'name': 'JC'}]}\n" - "X['index'][0] has the key \"'foo'\" whereas Y['index'][0] does not" + "X['index'][0] has the key \"'foo'\" whereas Y['index'][0] does not", ) @@ -1513,7 +1419,7 @@ def assertions(): "X = {'index': [{'age': 33, 'foo': 'bar', 'name': 'JC'}]}\n" " and\n" "Y = {'index': [{'age': 33, 'bar': 'foo', 'name': 'JC'}]}\n" - "X['index'][0] has the key \"'foo'\" whereas Y['index'][0] does not" + "X['index'][0] has the key \"'foo'\" whereas Y['index'][0] does not", ) @@ -1583,6 +1489,7 @@ def fail(): def test_raises_with_string(): "that(callable).raises('message') should compare the message" + def it_fails(): raise AssertionError("should fail with this exception") @@ -1644,12 +1551,14 @@ def test_deep_comparison_sequences_of_sequences(): try: expects(part1).equals(part2) except AssertionError as e: - expects(str(e)).to_not.be.different_of("""Equality Error + expects(str(e)).to_not.be.different_of( + """Equality Error X = [('Bootstraping Redis role', []), ('Restart scalarizr', []), ('Rebundle server', ['rebundle']), ('Use new role', ['rebundle']), ('Restart scalarizr after bundling', ['rebundle']), ('Bundling data', []), ('Modifying data', []), ('Reboot server', []), ('Backuping data on Master', []), ('Setup replication', []), ('Restart scalarizr in slave', []), ('Slave force termination', []), ('Slave delete EBS', ['ec2']), ('Setup replication for EBS test', ['ec2']), ('Writing on Master, reading on Slave', []), ('Slave -> Master promotion', []), ('Restart farm', ['restart_farm'])] and Y = [('Bootstraping Redis role', ['rebundle', 'rebundle', 'rebundle']), ('Restart scalarizr', []), ('Rebundle server', ['rebundle']), ('Use new role', ['rebundle']), ('Restart scalarizr after bundling', ['rebundle']), ('Bundling data', []), ('Modifying data', []), ('Reboot server', []), ('Backuping data on Master', []), ('Setup replication', []), ('Restart scalarizr in slave', []), ('Slave force termination', []), ('Slave delete EBS', ['ec2']), ('Setup replication for EBS test', ['ec2']), ('Writing on Master, reading on Slave', []), ('Slave -> Master promotion', []), ('Restart farm', ['restart_farm'])] Y[0][1] has 3 items whereas X[0][1] is empty -""".strip()) +""".strip() + ) def test_within_failing_due_to_internally_raised_exception(): @@ -1657,11 +1566,10 @@ def test_within_failing_due_to_internally_raised_exception(): def crash(*a): time.sleep(0.1) - raise RuntimeError('unrelated exception') + raise RuntimeError("unrelated exception") expects(within(five=miliseconds)(crash)).when.called.to.have.raised( - RuntimeError, - "unrelated exception" + RuntimeError, "unrelated exception" ) @@ -1675,3 +1583,95 @@ def test_all_integers_not_iterable(): ":func:`sure.original.all_integers` returns False when receiving a non-iterable param" expects(all_integers(9)).to.be.false + + +def test_word_to_number(): + expects(sure.word_to_number("one")).to.equal(1) + expects(sure.word_to_number("two")).to.equal(2) + expects(sure.word_to_number("three")).to.equal(3) + expects(sure.word_to_number("four")).to.equal(4) + expects(sure.word_to_number("five")).to.equal(5) + expects(sure.word_to_number("six")).to.equal(6) + expects(sure.word_to_number("seven")).to.equal(7) + expects(sure.word_to_number("eight")).to.equal(8) + expects(sure.word_to_number("nine")).to.equal(9) + expects(sure.word_to_number("ten")).to.equal(10) + expects(sure.word_to_number("eleven")).to.equal(11) + expects(sure.word_to_number("twelve")).to.equal(12) + expects(sure.word_to_number("thirteen")).to.equal(13) + expects(sure.word_to_number("fourteen")).to.equal(14) + expects(sure.word_to_number("fifteen")).to.equal(15) + expects(sure.word_to_number("sixteen")).to.equal(16) + expects(sure.word_to_number("seventeen")).to.equal(17) + expects(sure.word_to_number("eighteen")).to.equal(18) + expects(sure.word_to_number("nineteen")).to.equal(19) + expects(sure.word_to_number("twenty")).to.equal(20) + expects(sure.word_to_number("twenty_one")).to.equal(21) + expects(sure.word_to_number("fourty_two")).to.equal(42) + expects(sure.word_to_number("seventy_one")).to.equal(71) + expects(sure.word_to_number("ninety_four")).to.equal(94) + expects(sure.word_to_number("one_hundred")).to.equal(100) + expects(sure.word_to_number("one_thousand")).to.equal(1000) + expects(sure.word_to_number("one_million")).to.equal(1000000) + expects(sure.word_to_number("one_thousand_three_hundred_thirty_seven")).to.equal(1337) + + +def test_microsecond_unit(): + "testing microseconds convertion" + cfrom, cto = sure.UNITS[sure.microsecond] + + expects(cfrom(1)).to.equal(100000) + expects(cto(1)).to.equal(1) + + cfrom, cto = sure.UNITS[sure.microseconds] + + expects(cfrom(1)).to.equal(100000) + expects(cto(1)).to.equal(1) + + +def test_milisecond_unit(): + "testing miliseconds convertion" + cfrom, cto = sure.UNITS[sure.milisecond] + + expects(cfrom(1)).to.equal(1000) + expects(cto(100)).to.equal(1) + + cfrom, cto = sure.UNITS[sure.miliseconds] + + expects(cfrom(1)).to.equal(1000) + expects(cto(100)).to.equal(1) + + +def test_second_unit(): + "testing seconds convertion" + cfrom, cto = sure.UNITS[sure.second] + + expects(cfrom(1)).to.equal(1) + expects(cto(100000)).to.equal(1) + + cfrom, cto = sure.UNITS[sure.seconds] + + expects(cfrom(1)).to.equal(1) + expects(cto(100000)).to.equal(1) + + +def test_minute_unit(): + "testing minutes convertion" + cfrom, cto = sure.UNITS[sure.minute] + + expects(cfrom(60)).to.equal(1) + expects(cto(1)).to.equal(6000000) + + cfrom, cto = sure.UNITS[sure.minutes] + + expects(cfrom(60)).to.equal(1) + expects(cto(1)).to.equal(6000000) + + +def test_within_wrong_usage(): + "within(three=miliseconds, one=second) should raise WrongUsageError" + + expects(within).when.called_with(three=miliseconds, one=second).to.have.raised( + 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)", + )