diff --git a/docs/source/api-reference.rst b/docs/source/api-reference.rst
index e709867..043348e 100644
--- a/docs/source/api-reference.rst
+++ b/docs/source/api-reference.rst
@@ -30,8 +30,6 @@ API Reference
.. autoclass:: sure.core.DeepExplanation
.. _deep comparison:
.. autoclass:: sure.core.DeepComparison
-.. autofunction:: sure.core._get_file_name
-.. autofunction:: sure.core._get_line_number
.. autofunction:: sure.core.itemize_length
@@ -66,6 +64,7 @@ API Reference
.. py:module:: sure.reporters
.. autoclass:: sure.reporters.feature.FeatureReporter
+
``sure.original``
-----------------
@@ -75,7 +74,6 @@ API Reference
.. autofunction:: sure.original.all_integers
.. autofunction:: sure.original.explanation
-
``sure.doubles``
----------------
@@ -84,6 +82,7 @@ API Reference
.. autoclass:: sure.doubles.FakeOrderedDict
.. autoattribute:: sure.doubles.anything
+
``sure.doubles.dummies``
------------------------
diff --git a/sure/__init__.py b/sure/__init__.py
index ba44fbf..e719b0e 100644
--- a/sure/__init__.py
+++ b/sure/__init__.py
@@ -35,11 +35,11 @@
from sure import runtime
from sure.core import DeepComparison
from sure.core import DeepExplanation
-from sure.core import _get_file_name
-from sure.core import _get_line_number
from sure.errors import SpecialSyntaxDisabledError
from sure.errors import InternalRuntimeError
from sure.doubles.dummies import anything
+from sure.loader import get_file_name
+from sure.loader import get_line_number
from sure.version import version
from sure.special import is_cpython, patchable_builtin
from sure.registry import context as _registry
@@ -339,8 +339,8 @@ def ensure_providers(func, attr, args, kwargs):
def check_dependencies(func):
action = func.__name__
- filename = _get_file_name(func)
- lineno = _get_line_number(func)
+ filename = get_file_name(func)
+ lineno = get_line_number(func)
for dependency in depends_on:
if dependency in context.__sure_providers_of__:
@@ -354,7 +354,7 @@ def check_dependencies(func):
err += "\n".join(
[
" -> %s at %s:%d"
- % (p.__name__, _get_file_name(p), _get_line_number(p))
+ % (p.__name__, get_file_name(p), get_line_number(p))
for p in providers
]
)
diff --git a/sure/cli.py b/sure/cli.py
index 5a0ff39..545d557 100644
--- a/sure/cli.py
+++ b/sure/cli.py
@@ -83,6 +83,8 @@ def entrypoint(paths, reporter, immediate, log_level, log_file, special_syntax,
raise ExitError(runner.context, result)
elif cov:
+ sys.stdout = sys.__stdout__
+ sys.stderr = sys.__stderr__
cov.stop()
cov.save()
cov.report()
diff --git a/sure/core.py b/sure/core.py
index 367a3b9..5126e29 100644
--- a/sure/core.py
+++ b/sure/core.py
@@ -14,10 +14,8 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import os
-import inspect
-
from collections import OrderedDict
+from functools import cache
from six import (
text_type, integer_types, string_types, binary_type,
get_function_code
@@ -50,6 +48,8 @@ def __init__(self, X, Y, epsilon=None, parent=None):
float: self.compare_floats,
dict: self.compare_ordered_dicts,
list: self.compare_iterables,
+ set: self.compare_iterables,
+ frozenset: self.compare_iterables,
tuple: self.compare_iterables,
OrderedDict: self.compare_ordered_dicts
}
@@ -144,10 +144,8 @@ def compare_ordered_dicts(self, X, Y):
return DeepExplanation(msg)
return True
+ @cache
def get_context(self):
- if self._context:
- return self._context
-
X_keys = []
Y_keys = []
@@ -168,8 +166,7 @@ class ComparisonContext:
current_Y_keys = get_keys(Y_keys)
parent = comp
- self._context = ComparisonContext()
- return self._context
+ return ComparisonContext()
def compare_iterables(self, X, Y):
len_X, len_Y = map(len, (X, Y))
@@ -242,22 +239,6 @@ def explanation(self):
return self._explanation
-def _get_file_name(func):
- try:
- name = inspect.getfile(func)
- except AttributeError:
- name = get_function_code(func).co_filename
-
- return os.path.abspath(name)
-
-
-def _get_line_number(func):
- try:
- return inspect.getlineno(func)
- except AttributeError:
- return get_function_code(func).co_firstlineno
-
-
def itemize_length(items):
length = len(items)
return '{0} item{1}'.format(length, length > 1 and "s" or "")
diff --git a/sure/loader.py b/sure/loader.py
index 93274e9..04ac427 100644
--- a/sure/loader.py
+++ b/sure/loader.py
@@ -1,9 +1,11 @@
import os
import sys
import ast
+import types
import importlib
import importlib.util
-from typing import Dict, List, Union, Tuple
+
+from typing import Dict, List, Optional, Tuple, Union
from importlib.machinery import PathFinder
from pathlib import Path
from sure.errors import InternalRuntimeError
@@ -13,6 +15,42 @@
__TEST_CLASSES__ = {}
+def get_file_name(func) -> str:
+ """returns the file name of a given function or method"""
+ return FunMeta.from_function_or_method(func).filename
+
+
+def get_line_number(func) -> str:
+ """returns the first line number of a given function or method"""
+ return FunMeta.from_function_or_method(func).line_number
+
+
+class FunMeta(object):
+ """container for metadata specific to Python functions or methods"""
+ filename: str
+ line_number: int
+ name: str
+
+ def __init__(self, filename: str, line_number: int, name: str):
+ self.filename = collapse_path(filename)
+ self.line_number = line_number
+ self.name = name
+
+ def __repr__(self):
+ return f''
+
+ @classmethod
+ def from_function_or_method(cls, func):
+ if not isinstance(func, (types.FunctionType, types.MethodType)):
+ raise TypeError(f'get_function_or_method_metadata received an unexpected object: {func}')
+
+ return cls(
+ filename=func.__code__.co_filename,
+ line_number=func.__code__.co_firstlineno,
+ name=func.__name__,
+ )
+
+
def name_appears_to_indicate_test(name: str) -> bool:
return name.startswith('Test') or name.endswith('Test')
@@ -93,6 +131,10 @@ def resolve_path(path, relative_to="~") -> Path:
return Path(path).absolute().relative_to(Path(relative_to).expanduser())
+def collapse_path(e: Union[str, Path]) -> str:
+ return str(e).replace(os.getenv("HOME"), "~")
+
+
def get_package(path) -> Path:
if not isinstance(path, Path):
path = Path(path)
diff --git a/sure/original.py b/sure/original.py
index 96ef93f..df8a5b1 100644
--- a/sure/original.py
+++ b/sure/original.py
@@ -35,9 +35,9 @@
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
+from sure.loader import get_file_name
+from sure.loader import get_line_number
def identify_callable_location(callable_object):
diff --git a/sure/runtime.py b/sure/runtime.py
index 6aa91e1..2f88f82 100644
--- a/sure/runtime.py
+++ b/sure/runtime.py
@@ -38,6 +38,7 @@
)
from sure.loader import (
loader,
+ collapse_path,
get_type_definition_filename_and_firstlineno,
)
from sure.reporter import Reporter
@@ -117,10 +118,12 @@ def __init__(self, test, module_or_instance=None):
self.name = test.__class__.__name__
self.filename, self.line = get_type_definition_filename_and_firstlineno(test.__class__)
self.kind = test.__class__
+
elif isinstance(test, type):
self.name = test.__name__
self.filename, self.line = get_type_definition_filename_and_firstlineno(test)
self.kind = test
+
else:
raise NotImplementedError(f"{test} of type {type(test)} is not yet supported by {TestLocation}")
@@ -765,7 +768,7 @@ def __getattr__(self, attr):
try:
return self.__getattribute__(attr)
except AttributeError:
- return getattr(self.scenario_results[-1], attr, fallback)
+ return getattr(self.scenario_results[-1], attr)
@property
def is_failure(self):
@@ -843,7 +846,7 @@ def __getattr__(self, attr):
try:
return self.__getattribute__(attr)
except AttributeError:
- return getattr(self.scenario_results[-1], attr, fallback)
+ return getattr(self.scenario_results[-1], attr)
@property
def is_failure(self):
@@ -920,7 +923,7 @@ def __getattr__(self, attr):
try:
return self.__getattribute__(attr)
except AttributeError:
- return getattr(self.feature_results[-1], attr, fallback)
+ return getattr(self.feature_results[-1], attr)
@property
def is_failure(self):
@@ -957,7 +960,3 @@ def first_nonsuccessful_result(self) -> Optional[FeatureResult]:
def stripped(string):
return collapse_path("\n".join(filter(bool, [s.strip() for s in string.splitlines()])))
-
-
-def collapse_path(e: str):
- return str(e).replace(os.getenv("HOME"), "~")
diff --git a/tests/test_original_api.py b/tests/test_original_api.py
index df9a5f3..c466178 100644
--- a/tests/test_original_api.py
+++ b/tests/test_original_api.py
@@ -15,11 +15,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+import os
import sure
from sure import that
from sure import expect
from sure import VariablesBag
from sure.special import is_cpython
+from sure.loader import collapse_path
def test_setup_with_context():
@@ -52,7 +54,7 @@ def it_crashes():
assert that(it_crashes).raises(
TypeError,
(
- "the function it_crashes defined at test_original_api.py line 49, is being "
+ "the function it_crashes defined at test_original_api.py line 51, is being "
"decorated by either @that_with_context or @scenario, so it should "
"take at least 1 parameter, which is the test context"
),
@@ -925,12 +927,11 @@ def the_providers_are_working(the):
def test_depends_on_failing_due_to_lack_of_attribute_in_context():
"it fails when an action depends on some attribute that is not " "provided by any other previous action"
- import os
from sure import action_for, scenario
- fullpath = os.path.abspath(__file__)
+ fullpath = collapse_path(collapse_path(os.path.abspath(__file__)))
error = (
- 'the action "variant_action" defined at %s:940 '
+ 'the action "variant_action" defined at %s:941 '
'depends on the attribute "data_structure" to be available in the'
" context. It turns out that there are no actions providing "
"that. Please double-check the implementation" % fullpath
@@ -952,10 +953,9 @@ def depends_on_fails(the):
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"
- import os
from sure import action_for, scenario
- fullpath = os.path.abspath(__file__)
+ fullpath = collapse_path(os.path.abspath(__file__))
error = (
'the action "my_action" defined at {0}:971 '
'depends on the attribute "some_attr" to be available in the context.'