Added chicken and egg recursion detection

This commit is contained in:
2020-02-06 17:50:14 +01:00
parent afc1e22949
commit 7481b458e1
16 changed files with 358 additions and 77 deletions
+4 -1
View File
@@ -39,4 +39,7 @@ seventeen isa number
eighteen isa number eighteen isa number
nineteen isa number nineteen isa number
twenty isa number twenty isa number
def concept twenties from bnf twenty number where number < 10 as twenty + number def concept twenties from bnf twenty number where number < 10 as twenty + number
def concept thirty as 30
def concept forty as 40
+3 -1
View File
@@ -51,6 +51,7 @@ class BuiltinConcepts(Enum):
REDUCE_REQUESTED = "reduce requested" # remove meaningless error when possible REDUCE_REQUESTED = "reduce requested" # remove meaningless error when possible
NOT_A_SET = "not a set" # the concept has no entry in sets NOT_A_SET = "not a set" # the concept has no entry in sets
WHERE_CLAUSE_FAILED = "where clause failed" # failed to validate where clause during evaluation WHERE_CLAUSE_FAILED = "where clause failed" # failed to validate where clause during evaluation
CHICKEN_AND_EGG = "chicken and egg" # infinite recursion when declaring concept
NODE = "node" NODE = "node"
GENERIC_NODE = "generic node" GENERIC_NODE = "generic node"
@@ -92,7 +93,8 @@ BuiltinErrors = [str(e) for e in {
BuiltinConcepts.CONCEPT_EVAL_ERROR, BuiltinConcepts.CONCEPT_EVAL_ERROR,
BuiltinConcepts.CONCEPT_ALREADY_IN_SET, BuiltinConcepts.CONCEPT_ALREADY_IN_SET,
BuiltinConcepts.NOT_A_SET, BuiltinConcepts.NOT_A_SET,
BuiltinConcepts.WHERE_CLAUSE_FAILED BuiltinConcepts.WHERE_CLAUSE_FAILED,
BuiltinConcepts.CHICKEN_AND_EGG
}] }]
""" """
+16 -8
View File
@@ -4,7 +4,6 @@ import logging
import core.ast.nodes import core.ast.nodes
from core.ast.nodes import CallNodeConcept, GenericNodeConcept from core.ast.nodes import CallNodeConcept, GenericNodeConcept
from core.ast.visitors import UnreferencedNamesVisitor from core.ast.visitors import UnreferencedNamesVisitor
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
@@ -100,11 +99,22 @@ def expect_one(context, return_values, logger=None):
return_values[0], return_values[0],
parents=return_values) parents=return_values)
else: else:
return sheerka.ret( # test if only one evaluator in error
context.who, from evaluators.OneErrorEvaluator import OneErrorEvaluator
False, one_error_evaluator = OneErrorEvaluator()
sheerka.new(BuiltinConcepts.TOO_MANY_ERRORS, body=return_values), reduce_requested = sheerka.ret(context.who, True, sheerka.new(BuiltinConcepts.REDUCE_REQUESTED))
parents=return_values) if one_error_evaluator.matches(context, return_values + [reduce_requested]):
return sheerka.ret(
context.who,
False,
one_error_evaluator.eval(context, return_values).body,
parents=return_values)
else:
return sheerka.ret(
context.who,
False,
sheerka.new(BuiltinConcepts.TOO_MANY_ERRORS, body=return_values),
parents=return_values)
def get_names(sheerka, concept_node): def get_names(sheerka, concept_node):
@@ -210,5 +220,3 @@ def _extract_predicates(sheerka, node, variables_to_include, variables_to_exclud
predicates.append(res) predicates.append(res)
return predicates return predicates
+27 -18
View File
@@ -242,24 +242,24 @@ class Concept:
def body(self): def body(self):
return self.values[ConceptParts.BODY] if ConceptParts.BODY in self.values else None return self.values[ConceptParts.BODY] if ConceptParts.BODY in self.values else None
def add_codes(self, codes): # def add_codes(self, codes):
""" # """
Gets the ASTs for 'where', 'pre', 'post' and 'body' # Gets the ASTs for 'where', 'pre', 'post' and 'body'
There ASTs are know when the concept is freshly parsed. # There ASTs are know when the concept is freshly parsed.
So the values are kept in cache. # So the values are kept in cache.
#
For concepts loaded from sdp, these ASTs must be created again # For concepts loaded from sdp, these ASTs must be created again
TODO : Seems to be a service method. Can be put somewhere else # TODO : Seems to be a service method. Can be put somewhere else
:param codes: # :param codes:
:return: # :return:
""" # """
if codes is None: # if codes is None:
return # return
#
for key in codes: # for key in codes:
self.compiled[key] = codes[key] # self.compiled[key] = codes[key]
#
return self # return self
def get_digest(self): def get_digest(self):
""" """
@@ -406,3 +406,12 @@ class DoNotResolve:
set concept.compiled[BODY] to DoNotResolve(value) set concept.compiled[BODY] to DoNotResolve(value)
""" """
value: object value: object
@dataclass()
class InfiniteRecursionResolved:
"""This class is used to when we managed to break an infinite recursion concept definition"""
value: object
def get_value(self):
return self.value
+2
View File
@@ -222,6 +222,8 @@ class ExecutionContext:
to_str = self.return_value_to_str(r) to_str = self.return_value_to_str(r)
logger.debug(f"[{self._id:2}]" + self._tab + "-> " + to_str) logger.debug(f"[{self._id:2}]" + self._tab + "-> " + to_str)
def get_parent(self):
return self._parent
@staticmethod @staticmethod
def return_value_to_str(r): def return_value_to_str(r):
+11 -3
View File
@@ -1,6 +1,7 @@
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept from core.concept import Concept
from sdp.sheerkaDataProvider import SheerkaDataProvider from core.sheerka.ExecutionContext import ExecutionContext
from sdp.sheerkaDataProvider import SheerkaDataProvider, Event
import pprint import pprint
import os import os
@@ -28,7 +29,7 @@ class SheerkaDump:
defs = self.sheerka.sdp.get(self.sheerka.CONCEPTS_DEFINITIONS_ENTRY) defs = self.sheerka.sdp.get(self.sheerka.CONCEPTS_DEFINITIONS_ENTRY)
self.sheerka.log.info(defs) self.sheerka.log.info(defs)
def dump_desc(self, *concept_names): def dump_desc(self, *concept_names, eval=False):
first = True first = True
for concept_name in concept_names: for concept_name in concept_names:
if isinstance(concept_name, Concept): if isinstance(concept_name, Concept):
@@ -43,13 +44,20 @@ class SheerkaDump:
concepts = [concepts] concepts = [concepts]
for c in concepts: for c in concepts:
if eval:
event = Event(f"Evaluating {c}", "")
context = ExecutionContext("dump_desc", event, self.sheerka)
evaluated = self.sheerka.evaluate_concept(context, c)
value = evaluated.body if evaluated.key == c.key else evaluated
if not first: if not first:
self.sheerka.log.info("") self.sheerka.log.info("")
self.sheerka.log.info(f"name : {c.name}") self.sheerka.log.info(f"name : {c.name}")
self.sheerka.log.info(f"bnf : {c.metadata.definition}") self.sheerka.log.info(f"bnf : {c.metadata.definition}")
self.sheerka.log.info(f"key : {c.key}") self.sheerka.log.info(f"key : {c.key}")
self.sheerka.log.info(f"body : {c.metadata.body}") self.sheerka.log.info(f"body : {c.metadata.body}")
self.sheerka.log.info(f"value : {c.body}") if eval:
self.sheerka.log.info(f"value : {value}")
self.sheerka.log.info(f"digest : {c.get_digest()}") self.sheerka.log.info(f"digest : {c.get_digest()}")
if self.sheerka.isaset(c): if self.sheerka.isaset(c):
@@ -1,5 +1,5 @@
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept, DoNotResolve, ConceptParts from core.concept import Concept, DoNotResolve, ConceptParts, InfiniteRecursionResolved
import core.builtin_helpers import core.builtin_helpers
CONCEPT_EVALUATION_STEPS = [ CONCEPT_EVALUATION_STEPS = [
@@ -13,6 +13,55 @@ class SheerkaEvaluateConcept:
self.sheerka = sheerka self.sheerka = sheerka
self.logger_name = self.evaluate_concept.__name__ self.logger_name = self.evaluate_concept.__name__
@staticmethod
def infinite_recursion_detected(context, concept):
if concept is None:
return False
parent = context.get_parent()
while parent is not None:
if parent.who == context.who and parent.obj == concept:
return True
parent = parent.get_parent()
return False
@staticmethod
def get_infinite_recursion_resolution(obj):
if isinstance(obj, InfiniteRecursionResolved):
return obj
if isinstance(obj, Concept) and isinstance(obj.body, InfiniteRecursionResolved):
return obj.body
return None
def manage_infinite_recursion(self, context):
"""
We look for the fist parent that has a body that means something
We use the eval function with no local or global
:param context:
:return:
"""
parent = context
concepts_found = set()
while parent and parent.obj:
if parent.who == context.who and parent.desc == context.desc:
body = parent.obj.metadata.body
try:
return self.sheerka.ret(self.logger_name, True, InfiniteRecursionResolved(eval(body)))
except Exception:
pass
concepts_found.add(parent.obj)
parent = parent.get_parent()
return self.sheerka.ret(
self.logger_name,
False,
self.sheerka.new(BuiltinConcepts.CHICKEN_AND_EGG, body=concepts_found))
def initialize_concept_asts(self, context, concept: Concept, logger=None): def initialize_concept_asts(self, context, concept: Concept, logger=None):
""" """
Updates the codes of the newly created concept Updates the codes of the newly created concept
@@ -75,6 +124,15 @@ class SheerkaEvaluateConcept:
if isinstance(to_resolve, DoNotResolve): if isinstance(to_resolve, DoNotResolve):
return to_resolve.value return to_resolve.value
# manage infinite loop
if self.infinite_recursion_detected(context, current_concept):
with context.push(desc="Infinite recursion detected", obj=current_concept) as sub_context:
# I create a sub context in order to log what happened
sub_context.log_new(logger)
ret_val = self.manage_infinite_recursion(context)
sub_context.add_values(return_values=ret_val)
return ret_val.body
desc = f"Evaluating {current_prop} (concept={current_concept})" desc = f"Evaluating {current_prop} (concept={current_concept})"
context.log(logger, desc, self.logger_name) context.log(logger, desc, self.logger_name)
with context.push(desc=desc, obj=current_concept) as sub_context: with context.push(desc=desc, obj=current_concept) as sub_context:
@@ -92,7 +150,8 @@ class SheerkaEvaluateConcept:
# otherwise, execute all return values to find out what is the value # otherwise, execute all return values to find out what is the value
else: else:
r = self.sheerka.execute(sub_context, to_resolve, CONCEPT_EVALUATION_STEPS, logger) use_copy = [r for r in to_resolve] if hasattr(to_resolve, "__iter__") else to_resolve
r = self.sheerka.execute(sub_context, use_copy, CONCEPT_EVALUATION_STEPS, logger)
one_r = core.builtin_helpers.expect_one(context, r) one_r = core.builtin_helpers.expect_one(context, r)
sub_context.add_values(return_values=one_r) sub_context.add_values(return_values=one_r)
if one_r.status: if one_r.status:
@@ -100,10 +159,11 @@ class SheerkaEvaluateConcept:
else: else:
error = one_r.value error = one_r.value
return self.sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR, return error if self.sheerka.isinstance(error, BuiltinConcepts.CHICKEN_AND_EGG) \
body=error, else self.sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR,
concept=current_concept, body=error,
property_name=current_prop) concept=current_concept,
property_name=current_prop)
def resolve_list(self, context, list_to_resolve, current_prop, current_concept, logger): def resolve_list(self, context, list_to_resolve, current_prop, current_concept, logger):
"""When dealing with a list, there are two possibilities""" """When dealing with a list, there are two possibilities"""
@@ -168,7 +228,7 @@ class SheerkaEvaluateConcept:
else: else:
# Do not send the current concept for the properties # Do not send the current concept for the properties
resolved = self.resolve(context, prop_ast, prop_name, None, logger) resolved = self.resolve(context, prop_ast, prop_name, None, logger)
if context.sheerka.isinstance(resolved, BuiltinConcepts.CONCEPT_EVAL_ERROR): if isinstance(resolved, Concept) and not context.sheerka.is_success(resolved):
resolved.set_prop("concept", concept) # since current concept was not sent resolved.set_prop("concept", concept) # since current concept was not sent
return resolved return resolved
else: else:
@@ -178,10 +238,10 @@ class SheerkaEvaluateConcept:
if part_key in concept.compiled and concept.compiled[part_key] is not None: if part_key in concept.compiled and concept.compiled[part_key] is not None:
metadata_ast = concept.compiled[part_key] metadata_ast = concept.compiled[part_key]
resolved = self.resolve(context, metadata_ast, part_key, concept, logger) resolved = self.resolve(context, metadata_ast, part_key, concept, logger)
if context.sheerka.isinstance(resolved, BuiltinConcepts.CONCEPT_EVAL_ERROR): if isinstance(resolved, Concept) and not context.sheerka.is_success(resolved):
return resolved return resolved
else: else:
concept.values[part_key] = resolved concept.values[part_key] = self.get_infinite_recursion_resolution(resolved) or resolved
# validate where clause # validate where clause
if concept.metadata.where is not None: if concept.metadata.where is not None:
+31 -18
View File
@@ -249,11 +249,12 @@ class Sheerka(Concept):
if not self.skip_builtins_in_db: if not self.skip_builtins_in_db:
self.sdp.save_result(self, execution_context) self.sdp.save_result(self, execution_context)
# hack to save valid concept definition # hack to save valid concept definition
if not self.during_restore: # if not self.during_restore:
if len(ret) == 1 and ret[0].status and self.isinstance(ret[0].value, BuiltinConcepts.NEW_CONCEPT): # if len(ret) == 1 and ret[0].status and self.isinstance(ret[0].value, BuiltinConcepts.NEW_CONCEPT):
with open(CONCEPTS_FILE, "a") as f: # with open(CONCEPTS_FILE, "a") as f:
f.write(text + "\n") # f.write(text + "\n")
return ret return ret
def execute(self, execution_context, return_values, execution_steps, logger=None): def execute(self, execution_context, return_values, execution_steps, logger=None):
@@ -364,19 +365,26 @@ class Sheerka(Concept):
concept_key = str(concept_key) concept_key = str(concept_key)
# first search in cache # first search in cache
result = self.cache_by_key[concept_key] if concept_key in self.cache_by_key else \ if concept_key in self.cache_by_key:
self.sdp.get_safe(self.CONCEPTS_ENTRY, concept_key) result = self.cache_by_key[concept_key]
else:
result = self.sdp.get_safe(self.CONCEPTS_ENTRY, concept_key)
if result is None:
metadata = [("key", concept_key), ("id", concept_id)] if concept_id else ("key", concept_key)
result = self._get_unknown(metadata)
if result and (concept_id is None or not isinstance(result, list)): self.cache_by_key[concept_key] = result
for r in (result if isinstance(result, list) else [result]):
if r.id:
self.cache_by_id[r.id] = r
if not (isinstance(result, list) and concept_id):
return result return result
if isinstance(result, list): # result is a list, but we have the concept_id to discriminate
if concept_id: for c in result:
for c in result: if c.id == concept_id:
if c.id == concept_id: return c
return c
else:
return result
metadata = [("key", concept_key), ("id", concept_id)] if concept_id else ("key", concept_key) metadata = [("key", concept_key), ("id", concept_id)] if concept_id else ("key", concept_key)
return self._get_unknown(metadata) return self._get_unknown(metadata)
@@ -386,10 +394,15 @@ class Sheerka(Concept):
return ErrorConcept("Concept id is undefined.") return ErrorConcept("Concept id is undefined.")
# first search in cache # first search in cache
result = self.cache_by_id[concept_id] if concept_id in self.cache_by_id else \ if concept_id in self.cache_by_id:
self.sdp.get_safe(self.CONCEPTS_BY_ID_ENTRY, concept_id) result = self.cache_by_id[concept_id]
else:
result = self.sdp.get_safe(self.CONCEPTS_BY_ID_ENTRY, concept_id)
if result is None:
result = self._get_unknown(('id', concept_id))
self.cache_by_id[concept_id] = result
return result or self._get_unknown(('id', concept_id)) return result
def get_concepts_definitions(self, context): def get_concepts_definitions(self, context):
if self.concepts_definitions_cache: if self.concepts_definitions_cache:
+8 -7
View File
@@ -6,8 +6,8 @@ from evaluators.BaseEvaluator import OneReturnValueEvaluator
from parsers.BaseParser import NotInitializedNode from parsers.BaseParser import NotInitializedNode
from parsers.ConceptLexerParser import ParsingExpression, ParsingExpressionVisitor from parsers.ConceptLexerParser import ParsingExpression, ParsingExpressionVisitor
from parsers.DefaultParser import DefConceptNode from parsers.DefaultParser import DefConceptNode
from parsers.PythonParser import PythonNode from parsers.PythonParser import PythonNode
import core.utils
class ConceptOrRuleNameVisitor(ParsingExpressionVisitor): class ConceptOrRuleNameVisitor(ParsingExpressionVisitor):
@@ -66,7 +66,7 @@ class AddConceptEvaluator(OneReturnValueEvaluator):
setattr(concept.metadata, prop, source) setattr(concept.metadata, prop, source)
# try to find what can be a property # try to find what can be a property
concept_name = [part.value for part in def_concept_node.name.tokens] concept_name = [part.value for part in core.utils.strip_tokens(def_concept_node.name.tokens, True)]
for p in self.get_props(sheerka, part_ret_val, concept_name): for p in self.get_props(sheerka, part_ret_val, concept_name):
props_found.add(p) props_found.add(p)
@@ -108,11 +108,12 @@ class AddConceptEvaluator(OneReturnValueEvaluator):
# Case of python code # Case of python code
# #
if isinstance(ret_value.value, ParserResultConcept) and isinstance(ret_value.value.value, PythonNode): if isinstance(ret_value.value, ParserResultConcept) and isinstance(ret_value.value.value, PythonNode):
python_node = ret_value.value.value if len(concept_name) > 1:
as_concept_node = python_to_concept(python_node.ast_) python_node = ret_value.value.value
variables = get_names(sheerka, as_concept_node) as_concept_node = python_to_concept(python_node.ast_)
variables = filter(lambda x: x in concept_name, variables) variables = get_names(sheerka, as_concept_node)
return list(variables) variables = filter(lambda x: x in concept_name, variables)
return list(variables)
# #
# case of concept # case of concept
+1 -1
View File
@@ -48,7 +48,7 @@ class BaseTest:
@staticmethod @staticmethod
def pretval(concept, source=None, parser="parsers.name"): def pretval(concept, source=None, parser="parsers.name"):
"""ParserResult ret_val""" """ParserResult ret_val (p stands for ParserResult)"""
return ReturnValueConcept( return ReturnValueConcept(
"some_name", "some_name",
True, True,
+10 -4
View File
@@ -1,5 +1,5 @@
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from core.concept import PROPERTIES_TO_SERIALIZE from core.concept import PROPERTIES_TO_SERIALIZE, Concept
from sdp.sheerkaDataProvider import SheerkaDataProvider from sdp.sheerkaDataProvider import SheerkaDataProvider
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
@@ -80,12 +80,10 @@ class TestSheerkaCreateNewConcept(TestUsingMemoryBasedSheerka):
sheerka.cache_by_key = {} # reset the cache sheerka.cache_by_key = {} # reset the cache
loaded = sheerka.get(concept.key) loaded = sheerka.get(concept.key)
assert loaded is not None
assert loaded == concept assert loaded == concept
# I can also get it by its id # I can also get it by its id
loaded = sheerka.sdp.get(sheerka.CONCEPTS_BY_ID_ENTRY, concept.id) loaded = sheerka.sdp.get(sheerka.CONCEPTS_BY_ID_ENTRY, concept.id)
assert loaded is not None
assert loaded == concept assert loaded == concept
def test_i_can_get_a_concept_by_its_id(self): def test_i_can_get_a_concept_by_its_id(self):
@@ -96,7 +94,6 @@ class TestSheerkaCreateNewConcept(TestUsingMemoryBasedSheerka):
sheerka.cache_by_key = {} # reset the cache sheerka.cache_by_key = {} # reset the cache
loaded = sheerka.get_by_id(concept.id) loaded = sheerka.get_by_id(concept.id)
assert loaded is not None
assert loaded == concept assert loaded == concept
def test_i_can_get_list_of_concept_when_same_key_when_no_cache(self): def test_i_can_get_list_of_concept_when_same_key_when_no_cache(self):
@@ -178,3 +175,12 @@ class TestSheerkaCreateNewConcept(TestUsingMemoryBasedSheerka):
result = sheerka.get(concept1.key, "wrong id") result = sheerka.get(concept1.key, "wrong id")
assert sheerka.isinstance(result, BuiltinConcepts.UNKNOWN_CONCEPT) assert sheerka.isinstance(result, BuiltinConcepts.UNKNOWN_CONCEPT)
def test_concept_that_references_itself_is_correctly_created(self):
sheerka = self.get_sheerka()
concept = Concept("foo", body="foo")
res = sheerka.create_new_concept(self.get_context(sheerka), concept)
assert res.status
+89 -1
View File
@@ -1,6 +1,6 @@
import pytest import pytest
from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept, ParserResultConcept from core.builtin_concepts import BuiltinConcepts, ReturnValueConcept, ParserResultConcept
from core.concept import Concept, simplec, DoNotResolve, ConceptParts, Property from core.concept import Concept, simplec, DoNotResolve, ConceptParts, Property, InfiniteRecursionResolved
from parsers.PythonParser import PythonNode from parsers.PythonParser import PythonNode
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
@@ -369,3 +369,91 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka):
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), concept) evaluated = sheerka.evaluate_concept(self.get_context(sheerka), concept)
assert sheerka.isinstance(evaluated, BuiltinConcepts.WHERE_CLAUSE_FAILED) assert sheerka.isinstance(evaluated, BuiltinConcepts.WHERE_CLAUSE_FAILED)
assert evaluated.body == concept assert evaluated.body == concept
def test_i_can_detect_infinite_recursion_with_numeric_constant(self):
sheerka = self.get_sheerka()
one_str = Concept("one", body="1")
one_digit = Concept("1", body="one")
sheerka.add_in_cache(one_str)
sheerka.add_in_cache(one_digit)
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), one_digit)
assert evaluated.key == one_digit.key
assert evaluated.body == InfiniteRecursionResolved(1)
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), one_str)
assert evaluated.key == one_str.key
assert evaluated.body == InfiniteRecursionResolved(1)
def test_i_can_detect_infinite_recursion_with_boolean_constant(self):
sheerka = self.get_sheerka()
true_str = Concept("true", body="True")
true_bool = Concept("True", body="true")
sheerka.add_in_cache(true_str)
sheerka.add_in_cache(true_bool)
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), true_str)
assert evaluated.key == true_str.key
assert evaluated.body == InfiniteRecursionResolved(True)
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), true_bool)
assert evaluated.key == true_bool.key
assert evaluated.body == InfiniteRecursionResolved(True)
def test_i_can_detect_infinite_recursion_with_constant_with_more_concepts(self):
sheerka = self.get_sheerka()
c1 = sheerka.add_in_cache(Concept("one", body="1"))
c2 = sheerka.add_in_cache(Concept("1", body="2"))
c3 = sheerka.add_in_cache(Concept("2", body="3"))
c4 = sheerka.add_in_cache(Concept("3", body="one"))
for concept in (c1, c2, c3, c4):
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), concept)
assert evaluated.key == concept.key
assert evaluated.body == InfiniteRecursionResolved(3)
def test_i_can_detect_infinite_recursion_when_no_constant(self):
sheerka = self.get_sheerka()
foo = sheerka.add_in_cache(Concept("foo", body="bar"))
bar = sheerka.add_in_cache(Concept("bar", body="baz"))
baz = sheerka.add_in_cache(Concept("baz", body="qux"))
qux = sheerka.add_in_cache(Concept("qux", body="foo"))
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), foo)
assert sheerka.isinstance(evaluated, BuiltinConcepts.CHICKEN_AND_EGG)
assert evaluated.body == {foo, bar, baz, qux}
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), bar)
assert sheerka.isinstance(evaluated, BuiltinConcepts.CHICKEN_AND_EGG)
assert evaluated.body == {foo, bar, baz, qux}
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), baz)
assert sheerka.isinstance(evaluated, BuiltinConcepts.CHICKEN_AND_EGG)
assert evaluated.body == {foo, bar, baz, qux}
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), qux)
assert sheerka.isinstance(evaluated, BuiltinConcepts.CHICKEN_AND_EGG)
assert evaluated.body == {foo, bar, baz, qux}
def test_i_can_detect_auto_recursion(self):
sheerka = self.get_sheerka()
foo = sheerka.add_in_cache(Concept("foo", body="foo"))
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), foo)
assert sheerka.isinstance(evaluated, BuiltinConcepts.CHICKEN_AND_EGG)
assert evaluated.body == {foo}
def test_i_can_manage_auto_recursion_when_constant(self):
sheerka = self.get_sheerka()
one = Concept("1", body="1")
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), one)
assert evaluated.key == one.key
assert evaluated.body == 1
+38
View File
@@ -242,3 +242,41 @@ class TestSheerka(TestUsingFileBasedSheerka):
# only test a random one, it will be the same for the others # only test a random one, it will be the same for the others
sheerka = self.get_sheerka() sheerka = self.get_sheerka()
assert not sheerka.is_success(sheerka.new(BuiltinConcepts.TOO_MANY_SUCCESS)) assert not sheerka.is_success(sheerka.new(BuiltinConcepts.TOO_MANY_SUCCESS))
def test_cache_is_updated_after_get(self):
sheerka = self.get_sheerka()
# updated when by_key returns one element
sheerka.create_new_concept(self.get_context(sheerka), Concept("foo", body="1"))
sheerka.reset_cache()
sheerka.get("foo")
assert "foo" in sheerka.cache_by_key
assert "1001" in sheerka.cache_by_id
# updated when by_key returns two elements
sheerka.create_new_concept(self.get_context(sheerka), Concept("foo", body="2"))
sheerka.reset_cache()
sheerka.get("foo")
assert "foo" in sheerka.cache_by_key
assert "1001" in sheerka.cache_by_id
assert "1002" in sheerka.cache_by_id
# updated when by_id
sheerka.reset_cache()
sheerka.get_by_id("1001")
assert "1001" in sheerka.cache_by_id
assert "foo" not in sheerka.cache_by_key # cache_by_key not updated as "1001" is not the only one
def test_i_can_get_by_key_several_times(self):
sheerka = self.get_sheerka()
sheerka.create_new_concept(self.get_context(sheerka), Concept("foo", body="1"))
sheerka.create_new_concept(self.get_context(sheerka), Concept("foo", body="2"))
sheerka.reset_cache()
sheerka.get("foo", "1001") # only one element requested. But the cache must be updated with two elements
# let's check it
concepts = sheerka.get("foo")
assert len(concepts) == 2
assert concepts[0].id == "1001"
assert concepts[1].id == "1002"
+23 -5
View File
@@ -69,9 +69,8 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
return ReturnValueConcept(BaseParser.PREFIX + "some_name", True, ParserResultConcept(value=concept)) return ReturnValueConcept(BaseParser.PREFIX + "some_name", True, ParserResultConcept(value=concept))
@pytest.mark.parametrize("ret_val, expected", [ @pytest.mark.parametrize("ret_val, expected", [
( (ReturnValueConcept(BaseParser.PREFIX + "some_name", True, ParserResultConcept(value=DefConceptNode([]))),
ReturnValueConcept(BaseParser.PREFIX + "some_name", True, ParserResultConcept(value=DefConceptNode([]))), True),
True),
(ReturnValueConcept(BaseParser.PREFIX + "some_name", False, ParserResultConcept(value=DefConceptNode([]))), (ReturnValueConcept(BaseParser.PREFIX + "some_name", False, ParserResultConcept(value=DefConceptNode([]))),
False), False),
(ReturnValueConcept(BaseParser.PREFIX + "some_name", True, "not a ParserResultConcept"), False), (ReturnValueConcept(BaseParser.PREFIX + "some_name", True, "not a ParserResultConcept"), False),
@@ -133,11 +132,17 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
assert from_db.compiled == {} # ast is not saved in db assert from_db.compiled == {} # ast is not saved in db
def test_i_can_get_props_from_python_node(self): def test_i_can_get_props_from_python_node_when_long_name(self):
ret_val = self.get_concept_part("isinstance(a, str)") ret_val = self.get_concept_part("isinstance(a, str)")
context = self.get_context() context = self.get_context()
assert AddConceptEvaluator.get_props(context.sheerka, ret_val, ["a"]) == ["a"] assert AddConceptEvaluator.get_props(context.sheerka, ret_val, ["a", "b"]) == ["a"]
def test_i_cannot_get_props_from_python_node_when_name_has_only_one_token(self):
ret_val = self.get_concept_part("isinstance(a, str)")
context = self.get_context()
assert AddConceptEvaluator.get_props(context.sheerka, ret_val, ["a"]) == []
def test_i_can_get_props_from_another_concept(self): def test_i_can_get_props_from_another_concept(self):
concept = Concept("hello").def_prop("a").def_prop("b") concept = Concept("hello").def_prop("a").def_prop("b")
@@ -153,3 +158,16 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
ret_val = self.get_return_value("mult (('+'|'-') add)?", parsing_expression) ret_val = self.get_return_value("mult (('+'|'-') add)?", parsing_expression)
assert AddConceptEvaluator.get_props(self.get_context(), ret_val, []) == ["add", "mult"] assert AddConceptEvaluator.get_props(self.get_context(), ret_val, []) == ["add", "mult"]
def test_concept_that_references_itself_is_correctly_created(self):
context = self.get_context()
def_concept_as_return_value = self.get_def_concept("foo", body="foo")
ret_val = AddConceptEvaluator().eval(context, def_concept_as_return_value)
assert ret_val.status
new_concept = ret_val.body.body
assert new_concept.name == 'foo'
assert new_concept.metadata.body == 'foo'
assert new_concept.props == {}
assert new_concept.metadata.props == []
+1 -1
View File
@@ -103,7 +103,7 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
assert context.sheerka.isinstance(result.value, BuiltinConcepts.CONCEPT_EVAL_ERROR) assert context.sheerka.isinstance(result.value, BuiltinConcepts.CONCEPT_EVAL_ERROR)
assert result.value.concept == concept_plus assert result.value.concept == concept_plus
assert result.value.property_name == "b" assert result.value.property_name == "b"
assert context.sheerka.isinstance(result.value.error, BuiltinConcepts.TOO_MANY_ERRORS) assert context.sheerka.isinstance(result.value.error, BuiltinConcepts.ERROR)
def test_that_error_concepts_are_transformed_into_errors_only_if_the_key_is_different(self): def test_that_error_concepts_are_transformed_into_errors_only_if_the_key_is_different(self):
context = self.get_context() context = self.get_context()
+25
View File
@@ -671,3 +671,28 @@ as:
res = sheerka.evaluate_user_input("eval twenty three") res = sheerka.evaluate_user_input("eval twenty three")
assert len(res) > 1 assert len(res) > 1
def test_i_can_detect_when_only_one_evaluator_is_in_error(self):
sheerka = self.get_sheerka()
sheerka.evaluate_user_input("def concept 1 as one")
res = sheerka.evaluate_user_input("1")
assert len(res) == 1
assert not res[0].status
assert sheerka.isinstance(res[0].body, BuiltinConcepts.CONCEPT_EVAL_ERROR)
def test_i_can_manage_some_type_of_infinite_recursion(self):
sheerka = self.get_sheerka()
sheerka.evaluate_user_input("def concept one as 1")
sheerka.evaluate_user_input("def concept 1 as one")
res = sheerka.evaluate_user_input("one + 1")
assert len(res) == 1
assert res[0].status
assert res[0].body == 2
res = sheerka.evaluate_user_input("1 + 1")
assert len(res) == 1
assert res[0].status
assert res[0].body == 2