Fixed #32 : concept groups are not correctly updated

Fixed #35 : Refactor test helper class (CNC, CC, CIO)
Fixed #36 : Concept values are not used when declared with variable expression
Fixed #37 : Objects in memory lose their values are restart
Fixed #38 : func(a=b, c) (which is not allowed) raise an exception
This commit is contained in:
2021-03-05 11:16:19 +01:00
parent 646c428edb
commit 05577012f3
38 changed files with 1942 additions and 1463 deletions
+842 -31
View File
@@ -1,13 +1,16 @@
import ast
from dataclasses import dataclass
from typing import Union
from core.builtin_concepts import ReturnValueConcept
from core.builtin_helpers import CreateObjectIdentifiers
from core.concept import CC, Concept, ConceptParts, DoNotResolve, CIO, CMV
from core.concept import Concept, ConceptParts, DoNotResolve, AllConceptParts
from core.rule import Rule
from core.tokenizer import Tokenizer, TokenKind, Token
from core.utils import get_text_from_tokens, tokens_index
from parsers.BaseNodeParser import scnode, utnode, cnode, SCWC, CNC, short_cnode, CN, UTN, \
SCN, RN, UnrecognizedTokensNode, SourceCodeNode
from core.utils import get_text_from_tokens, tokens_index, str_concept
from parsers.BaseNodeParser import UnrecognizedTokensNode, SourceCodeNode, RuleNode, ConceptNode, \
SourceCodeWithConceptNode
from parsers.FunctionParser import FunctionNode
from parsers.PythonParser import PythonNode
from parsers.SyaNodeParser import SyaConceptParserHelper
from parsers.expressions import NameExprNode, AndNode, OrNode, NotNode, VariableNode, ComparisonNode, ComparisonType
@@ -115,6 +118,825 @@ class NIN: # for NOT INT
source = None
class CC:
"""
Concept class for test purpose
CC means concept for compiled (or concept with compiled)
It matches a concept if the compiles are equals
"""
# The only properties that are testes are concept_key and compiled
# The other properties (concept, source, start and end)
# are used in tests/parsers/parsers_utils.py to help creating helper objects
def __init__(self, concept, source=None, exclude_body=False, **kwargs):
self.concept_key = concept.key if isinstance(concept, Concept) else concept
self.compiled = kwargs
self.concept = concept if isinstance(concept, Concept) else None
self.source = source # to use when the key is different from the sub str to search when filling start and stop
self.start = None # for debug purpose, indicate where the concept starts
self.end = None # for debug purpose, indicate where the concept ends
self.exclude_body = exclude_body
if "body" in self.compiled:
self.compiled[ConceptParts.BODY] = self.compiled["body"]
del self.compiled["body"]
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, Concept):
if other.key != self.concept_key:
return False
if self.exclude_body:
to_compare = {k: v for k, v in other.get_compiled().items() if k != ConceptParts.BODY}
else:
to_compare = other.get_compiled()
if self.compiled == to_compare:
return True
else:
return False
if not isinstance(other, CC):
return False
if self.concept_key != other.concept_key:
return False
return self.compiled == other.compiled
def __hash__(self):
if self.concept:
return hash(self.concept)
return hash(self.concept_key)
def __repr__(self):
if self.concept:
txt = f"CC(concept='{self.concept}'"
else:
txt = f"CC(concept_key='{self.concept_key}'"
for k, v in self.compiled.items():
txt += f", {k}='{v}'"
return txt + ")"
def fix_pos(self, node):
start = node.start if hasattr(node, "start") else \
node[0] if isinstance(node, tuple) else None
end = node.end if hasattr(node, "end") else \
node[1] if isinstance(node, tuple) else None
if start is not None:
if self.start is None or start < self.start:
self.start = start
if end is not None:
if self.end is None or end > self.end:
self.end = end
return self
def transform_real_obj(self, other, to_compare_delegate):
"""
Transform other into CNC, to ease the comparison
:param other:
:param to_compare_delegate:
:return:
"""
if isinstance(other, CC):
return other
if isinstance(other, Concept):
if self.exclude_body:
compiled = {k: v for k, v in other.get_compiled().items() if k != ConceptParts.BODY}
else:
compiled = other.get_compiled()
self_compile_to_use = self.compiled or compiled
compiled = to_compare_delegate(self_compile_to_use, compiled, to_compare_delegate)
return CC(other,
self.source,
self.exclude_body,
**compiled)
raise NotImplementedError(f"CC, {other=}")
class CB:
"""
Concept with body only
Test class that tests only the body of the concept
"""
def __init__(self, concept: Union[str, Concept], body: object):
self.concept_key = concept.key if isinstance(concept, Concept) else concept
self.concept = concept if isinstance(concept, Concept) else None
self.body = body
def __eq__(self, other):
if not isinstance(other, CB):
return False
return self.concept_key == other.concept_key and self.body == other.body
def __hash__(self):
return hash((self.concept, self.body))
def __repr__(self):
concept_debug = f"concept={self.concept}" if self.concept else f"concept_key={self.concept_key}"
return f"CB({concept_debug}, body='{self.body}')"
def transform_real_obj(self, other, get_test_obj_delegate):
if isinstance(other, CB):
return other
if isinstance(other, Concept):
concept = other.key if not self.concept else other
if isinstance(other.body, Concept):
body = get_test_obj_delegate(other.body, self.body, get_test_obj_delegate)
else:
body = other.body
return CB(concept, body)
raise NotImplementedError(f"CB, {other=}")
class CV:
"""
Concept with all values
Test class that tests all the values (not the metadata, so not the properties) of a concept
"""
def __init__(self, concept, **kwargs):
self.concept_key = concept.key if isinstance(concept, Concept) else concept
self.concept = concept if isinstance(concept, Concept) else None
self.values = {}
for k, v in kwargs.items():
if f"#{k}#" in AllConceptParts:
self.values[f"#{k}#"] = v
else:
self.values[k] = v
def __eq__(self, other):
if not isinstance(other, CV):
return False
return self.concept_key == other.concept_key and self.values == other.values
def __hash__(self):
return hash((self.concept_key, self.values))
def __repr__(self):
concept_debug = f"concept={self.concept}" if self.concept else f"concept_key={self.concept_key}"
return f"CV({concept_debug}, values={self.values})"
def transform_real_obj(self, other, get_test_obj_delegate):
if isinstance(other, CV):
return other
if isinstance(other, Concept):
concept = other.key if not self.concept else other
values = get_test_obj_delegate(other.values(), self.values, get_test_obj_delegate)
return CV(concept, **values)
raise NotImplementedError(f"CV, {other=}")
class CMV:
"""
Concept with metadata variables
CMV stands for Concept Metadata Variables
Test class that only compare the key and the metadata variables
"""
def __init__(self, concept, **kwargs):
self.concept_key = concept.key if isinstance(concept, Concept) else concept
self.concept = concept if isinstance(concept, Concept) else None
self.variables = kwargs
def __eq__(self, other):
if id(self) == id(other):
return True
if not isinstance(other, CMV):
return False
if self.concept_key != other.concept_key:
return False
return self.variables == other.variables
def __hash__(self):
if self.concept:
return hash(self.concept)
return hash(self.concept_key)
def __repr__(self):
if self.concept:
txt = f"CMV(concept='{self.concept}'"
else:
txt = f"CMV(concept_key='{self.concept_key}'"
for k, v in self.variables.items():
txt += f", {k}='{v}'"
return txt + ")"
def transform_real_obj(self, other, get_test_obj_delegate):
if isinstance(other, CMV):
return other
if isinstance(other, Concept):
concept = other.key if not self.concept else other
variables = {name: value for name, value in other.get_metadata().variables}
return CMV(concept, **variables)
raise NotImplementedError(f"CMV, {other=}")
class CIO:
"""
Concept id only
only test the id
"""
def __init__(self, concept, source=None):
if isinstance(concept, str):
self.concept_name = concept
self.concept_id = None
self.concept = None
elif isinstance(concept, Concept):
self.concept_id = concept.id
self.concept = concept
self.source = source
self.start = None
self.end = None
def set_concept(self, concept):
self.concept = concept
self.concept_id = concept.id
def __eq__(self, other):
if id(self) == id(other):
return True
if not isinstance(other, CIO):
return False
return self.concept_id == other.concept_id
def __hash__(self):
return hash(self.concept_id)
def __repr__(self):
return f"CIO(concept='{self.concept}')" if self.concept else f"CIO(name='{self.concept_name}')"
def transform_real_obj(self, other, get_test_obj_delegate):
if isinstance(other, CIO):
return other
if isinstance(other, Concept):
return CIO(other)
raise NotImplementedError(f"CIO, {other=}")
class HelperWithPos:
def __init__(self, start=None, end=None):
self.start = start
self.end = end
self.start_is_fixed = start is not None
self.end_is_fixed = end is not None
def fix_pos(self, node):
if not self.start_is_fixed:
start = node.start if hasattr(node, "start") else \
node[0] if isinstance(node, tuple) else None
if start is not None and (self.start is None or start < self.start):
self.start = start
if not self.end_is_fixed:
end = node.end if hasattr(node, "end") else \
node[1] if isinstance(node, tuple) else None
if end is not None and (self.end is None or end > self.end):
self.end = end
return self
class SCN(HelperWithPos):
"""
SourceCodeNode tester class
It matches with SourceCodeNode but with less constraints
SCN == SourceCodeNode if source, start, end (start and end are not validated when None)
"""
def __init__(self, source, start=None, end=None):
super().__init__(start, end)
self.source = source
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, SourceCodeNode):
if self.source != other.source:
return False
if self.start is not None and self.start != other.start:
return False
if self.end is not None and self.end != other.end:
return False
return True
if not isinstance(other, SCN):
return False
return self.source == other.source and \
self.start == other.start and \
self.end == other.end
def __hash__(self):
return hash((self.source, self.start, self.end))
def __repr__(self):
txt = f"SCN(source='{self.source}'"
if self.start is not None:
txt += f", start={self.start}"
if self.end is not None:
txt += f", end={self.end}"
return txt + ")"
def transform_real_obj(self, other, to_compare_delegate):
"""
Transform other into CNC, to ease the comparison
:param other:
:param to_compare_delegate:
:return:
"""
if isinstance(other, SCN):
return other
if isinstance(other, SourceCodeNode):
return SCN(other.source,
other.start if self.start is not None else None,
other.end if self.end is not None else None)
raise NotImplementedError(f"SCN, {other=}")
class SCWC(HelperWithPos):
"""
SourceNodeWithConcept tester class
It matches with a SourceNodeWithConcept
but it's easier to instantiate during the tests
"""
def __init__(self, first, last, *args):
super().__init__(None, None)
self.first = first
self.last = last
self.content = list(args)
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, SourceCodeWithConceptNode):
if self.first != other.first:
return False
if self.last != other.last:
return False
if len(self.content) != len(other.nodes):
return False
for self_node, other_node in zip(self.content, other.nodes):
if self_node != other_node:
return False
# at last
return True
if not isinstance(other, SCWC):
return False
return (self.start == other.start and
self.end == other.end and
self.first == other.first and
self.last == other.last and
self.content == other.content)
def __repr__(self):
txt = "SCWC("
if self.start is not None:
txt += f"start={self.start}"
if self.end is not None:
txt += f", end={self.end}"
for item in [self.first, self.last, *self.content]:
txt += f", {item}"
return txt + ")"
def transform_real_obj(self, other, get_test_obj_delegate):
"""
Transform other into CNC, to ease the comparison
:param other:
:param get_test_obj_delegate:
:return:
"""
if isinstance(other, SCWC):
return other
if isinstance(other, SourceCodeWithConceptNode):
first = get_test_obj_delegate(other.first, self.first)
last = get_test_obj_delegate(other.last, self.last)
content = [get_test_obj_delegate(r, t) for r, t in zip(other.nodes, self.content)]
res = SCWC(first, last, *content)
res.start = other.start
res.end = other.end
return res
raise NotImplementedError(f"SCWC, {other=}")
@property
def source(self):
"""
this code is a copy and paste from SourceCodeWithConceptNode.pseudo_fix_source
TODO: create a common function or whatever...
:return:
"""
source = self.first.source if hasattr(self.first, "source") else self.first
for n in self.content:
source += " "
if hasattr(n, "source"):
source += n.source
elif hasattr(n, "concept"):
source += str(n.concept)
else:
source += " unknown"
source += self.last.source if hasattr(self.last, "source") else self.last
return source
class CN(HelperWithPos):
"""
ConceptNode tester class
It matches with ConceptNode but with less constraints
CN == ConceptNode if concept key, start, end and source are the same
"""
def __init__(self, concept, source=None, start=None, end=None):
"""
:param concept: Concept or concept_key (only the key is used anyway)
:param start:
:param end:
:param source:
"""
super().__init__(start, end)
self.concept_key = concept.key if isinstance(concept, Concept) else concept
self.source = source
self.concept = concept if isinstance(concept, Concept) else None
def fix_source(self, str_tokens):
self.source = "".join(str_tokens)
return self
def __eq__(self, other):
if id(self) == id(other):
return True
if not isinstance(other, CN):
return False
return self.concept_key == other.concept_key and \
self.start == other.start and \
self.end == other.end and \
self.source == other.source
def __hash__(self):
return hash((self.concept_key, self.start, self.end, self.source))
def __repr__(self):
if self.concept:
txt = f"CN(concept='{self.concept}'"
else:
txt = f"CN(concept_key='{self.concept_key}'"
txt += f", source='{self.source}'"
if self.start is not None:
txt += f", start={self.start}"
if self.end is not None:
txt += f", end={self.end}"
return txt + ")"
def transform_real_obj(self, other, get_test_obj_delegate):
"""
Transform other into CNC, to ease the comparison
:param other:
:param get_test_obj_delegate:
:return:
"""
if isinstance(other, CN):
return other
if isinstance(other, ConceptNode):
return CN(other.concept,
other.source if self.source is not None else None,
other.start if self.start is not None else None,
other.end if self.end is not None else None)
raise NotImplementedError(f"CN, {other=}")
class CNC(CN):
"""
ConceptNode for Compiled tester class
It matches with ConceptNode
But focuses on the 'compiled' property of the concept
CNC == ConceptNode if CNC.get_compiled() == ConceptNode.concept.get_compiled()
"""
def __init__(self, concept_key, source=None, start=None, end=None, exclude_body=False, **kwargs):
super().__init__(concept_key, source, start, end)
self.compiled = kwargs
self.exclude_body = exclude_body
if "body" in self.compiled:
self.compiled[ConceptParts.BODY] = self.compiled["body"]
del self.compiled["body"]
def __eq__(self, other):
if id(self) == id(other):
return True
if not isinstance(other, CNC):
return False
return self.concept_key == other.concept_key and \
self.start == other.start and \
self.end == other.end and \
self.source == other.source and \
self.compiled == other.compiled
def __repr__(self):
if self.concept:
txt = f"CNC(concept='{self.concept}'"
else:
txt = f"CNC(concept_key='{self.concept_key}'"
txt += f", source='{self.source}'"
if self.start is not None:
txt += f", start={self.start}"
if self.end is not None:
txt += f", end={self.end}"
for k, v in self.compiled.items():
txt += f", {k}='{v}'"
return txt + ")"
def transform_real_obj(self, other, get_test_obj_delegate):
"""
Transform other into CNC, to ease the comparison
:param other:
:param get_test_obj_delegate:
:return:
"""
if isinstance(other, CNC):
return other
if isinstance(other, ConceptNode):
if self.exclude_body:
compiled = {k: v for k, v in other.concept.get_compiled().items() if k != ConceptParts.BODY}
else:
compiled = other.concept.get_compiled()
self_compile_to_use = self.compiled or compiled
compiled = get_test_obj_delegate(self_compile_to_use, compiled, get_test_obj_delegate)
return CNC(other.concept,
other.source if self.source is not None else None,
other.start if self.start is not None else None,
other.end if self.end is not None else None,
self.exclude_body,
**compiled)
raise NotImplementedError(f"CNC, {other=}")
class UTN(HelperWithPos):
"""
Tester class for UnrecognizedTokenNode
compare the source, and start, end if defined
"""
def __init__(self, source, start=None, end=None):
"""
:param source:
:param start:
:param end:
"""
super().__init__(start, end)
self.source = source
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, UnrecognizedTokensNode):
return self.start == other.start and \
self.end == other.end and \
self.source == other.source
if not isinstance(other, UTN):
return False
return self.start == other.start and \
self.end == other.end and \
self.source == other.source
def __hash__(self):
return hash((self.source, self.start, self.end))
def __repr__(self):
txt = f"UTN(source='{self.source}'"
if self.start is not None:
txt += f", start={self.start}"
if self.end is not None:
txt += f", end={self.end}"
return txt + ")"
def transform_real_obj(self, other, get_test_obj_delegate):
"""
Transform other into CNC, to ease the comparison
:param other:
:param get_test_obj_delegate:
:return:
"""
if isinstance(other, UTN):
return other
if isinstance(other, UnrecognizedTokensNode):
return UTN(other.source,
other.start,
other.end)
raise NotImplementedError(f"UTN, {other=}")
class RN(HelperWithPos):
"""
Helper class to test RuleNode
"""
def __init__(self, rule, source=None, start=None, end=None):
"""
:param source:
:param start:
:param end:
"""
super().__init__(start, end)
self.rule_id = rule.id if isinstance(rule, Rule) else rule
self.source = source or str_concept((None, self.rule_id), prefix="r:") if self.rule_id else None
self.rule = rule if isinstance(rule, Rule) else None
def __eq__(self, other):
if id(self) == id(other):
return True
if not isinstance(other, RN):
return False
return self.rule_id == other.rule_id and \
self.start == other.start and \
self.end == other.end and \
self.source == other.source
def __hash__(self):
return hash((self.rule_id, self.start, self.end, self.source))
def __repr__(self):
if self.rule:
txt = f"RN(rule='{self.rule}'"
else:
txt = f"RN(rule_id='{self.rule_id}'"
txt += f", source='{self.source}'"
if self.start is not None:
txt += f", start={self.start}"
if self.end is not None:
txt += f", end={self.end}"
return txt + ")"
def transform_real_obj(self, other, get_test_obj_delegate):
"""
Transform other into CNC, to ease the comparison
:param other:
:param get_test_obj_delegate:
:return:
"""
if isinstance(other, RN):
return other
if isinstance(other, RuleNode):
return RN(other.rule,
other.source if self.source is not None else None,
other.start if self.start is not None else None,
other.end if self.end is not None else None)
raise NotImplementedError(f"RN, {other=}")
class FN:
"""
Test class only
It matches with FunctionNode but with less constraints
Thereby,
FN("first", "last", ["param1," ...]) can be compared to
FunctionNode(NameExprNode("first"), NameExprNode("second"), [FunctionParameter(NamesNodes("param1"), NamesNodes(", ")])
Note that FunctionParameter can easily be defined with a single string
* "param" -> FunctionParameter(NameExprNode("param"), None)
* "param, " -> FunctionParameter(NameExprNode("param"), NameExprNode(", "))
For more complicated situations, you can use a tuple (value, sep) to define the value part and the separator part
"""
def __init__(self, first, last, parameters):
self.first = first
self.last = last
self.parameters = []
for param in parameters:
if isinstance(param, tuple):
self.parameters.append(param)
elif isinstance(param, str) and (pos := param.find(",")) != -1:
self.parameters.append((param[:pos], param[pos:]))
else:
self.parameters.append((param, None))
def __repr__(self):
res = self.first
for param in self.parameters:
if param[1]:
res += f"{param[0]}{param[1]} "
else:
res += f"{param[0]}"
return res + self.last
def __eq__(self, other):
if id(self) == id(other):
return True
if isinstance(other, FN):
return self.first == other.first and self.last == other.last and self.parameters == other.parameters
# if isinstance(other, FunctionNode):
# if self.first != other.first.value or self.last != other.last.value:
# return False
# if len(self.parameters) != len(other.parameters):
# return False
# for self_parameter, other_parameter in zip(self.parameters, other.parameters):
# value = other_parameter.value.value if isinstance(self_parameter[0], str) else other_parameter.value
# sep = other_parameter.separator.value if other_parameter.separator else None
# if self_parameter[0] != value or self_parameter[1] != sep:
# return False
#
# return True
return False
def __hash__(self):
return hash((self.first, self.last, self.parameters))
def transform_real_obj(self, other, get_test_obj_delegate):
if isinstance(other, FN):
return other
if isinstance(other, FunctionNode):
params = []
for self_parameter, other_parameter in zip(self.parameters, other.parameters):
if isinstance(self_parameter[0], str):
value = other_parameter.value.value
else:
value = get_test_obj_delegate(other_parameter.value, self_parameter[0])
sep = other_parameter.separator.value if other_parameter.separator else None
params.append((value, sep))
return FN(other.first.value, other.last.value, params)
raise NotImplementedError(f"FN, {other=}")
comparison_type_mapping = {
"EQ": ComparisonType.EQUALS,
"NEQ": ComparisonType.NOT_EQUAlS,
@@ -260,7 +1082,7 @@ def get_node(
if isinstance(sub_expr, ReturnValueConcept):
return sub_expr
if isinstance(sub_expr, (scnode, utnode, DoNotResolve)):
if isinstance(sub_expr, DoNotResolve):
return sub_expr
if isinstance(sub_expr, CIO):
@@ -272,18 +1094,6 @@ def get_node(
sub_expr.end = node.end
return sub_expr
if isinstance(sub_expr, cnode):
# for cnode, map the concept key to the one from concepts_maps if needed
if sub_expr.concept_key.startswith("#"):
return cnode(
concepts_map[sub_expr.concept_key[1:]].key,
sub_expr.start,
sub_expr.end,
sub_expr.source
)
else:
return sub_expr
if isinstance(sub_expr, SCWC):
sub_expr.first = get_node(concepts_map, expression_as_tokens, sub_expr.first, sya=sya)
sub_expr.last = get_node(concepts_map, expression_as_tokens, sub_expr.last, sya=sya)
@@ -342,10 +1152,6 @@ def get_node(
sub_expr.fix_pos(node)
return sub_expr
if isinstance(sub_expr, short_cnode):
return get_node(concepts_map, expression_as_tokens, sub_expr.source,
concept_key=sub_expr.concept_key, skip=skip, is_bnf=True, sya=sya)
if isinstance(sub_expr, tuple):
return get_node(concepts_map, expression_as_tokens, sub_expr[0],
concept_key=concept_key, skip=sub_expr[1], is_bnf=is_bnf, sya=sya)
@@ -364,11 +1170,11 @@ def get_node(
if sya and len(concept_found.get_metadata().variables) > 0 and not is_bnf:
return SyaConceptParserHelper(concept_found, start, start + length - 1)
elif init_empty_body:
node = CNC(concept_found, start, start + length - 1, source=sub_expr, exclude_body=exclude_body)
node = CNC(concept_found, sub_expr, start, start + length - 1, exclude_body=exclude_body)
init_body(node, concept_found, sub_expr)
return node
else:
return CN(concept_found, start, start + length - 1, source=sub_expr)
return CN(concept_found, sub_expr, start, start + length - 1)
else:
# else an UnrecognizedTokensNode
return UTN(sub_expr, start, start + length - 1)
@@ -489,27 +1295,32 @@ def get_rete_conditions(*conditions_as_string):
return AndConditions(res)
def get_test_obj(test_obj, real_obj, to_compare_delegate=None):
def get_test_obj(real_obj, test_obj, get_test_obj_delegate=None):
"""
From a production object (Concept, ConceptNode, ....)
Create a test object (CNC, CC ...) that can be used to validate the unit tests
:param test_obj:
:param real_obj:
:param to_compare_delegate:
:param get_test_obj_delegate:
:return:
"""
if isinstance(test_obj, list):
if len(test_obj) != len(real_obj):
raise Exception(f"Not the same size ! {test_obj=}, {real_obj=}")
return [get_test_obj(t, r) for t, r in zip(test_obj, real_obj)]
raise Exception(f"Not the same size ! {real_obj=}, {test_obj=}")
return [get_test_obj(r, t) for r, t in zip(real_obj, test_obj)]
if isinstance(test_obj, dict):
if len(test_obj) != len(real_obj):
raise Exception(f"Not the same size ! {test_obj=}, {real_obj=}")
raise Exception(f"Not the same size ! {real_obj=}, {test_obj=}")
return {k: get_test_obj(v, real_obj[k]) for k, v in test_obj.items()}
return {k: get_test_obj(real_obj[k], v) for k, v in test_obj.items()}
if not hasattr(test_obj, "to_compare"):
if not hasattr(test_obj, "transform_real_obj"):
return real_obj
return test_obj.to_compare(real_obj, get_test_obj)
return test_obj.transform_real_obj(real_obj, get_test_obj)
def compare_with_test_object(actual, expected):
to_compare = get_test_obj(actual, expected)
assert to_compare == expected