Added chicken and egg recursion detection
This commit is contained in:
@@ -40,3 +40,6 @@ eighteen isa number
|
||||
nineteen isa number
|
||||
twenty isa number
|
||||
def concept twenties from bnf twenty number where number < 10 as twenty + number
|
||||
def concept thirty as 30
|
||||
def concept forty as 40
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ class BuiltinConcepts(Enum):
|
||||
REDUCE_REQUESTED = "reduce requested" # remove meaningless error when possible
|
||||
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
|
||||
CHICKEN_AND_EGG = "chicken and egg" # infinite recursion when declaring concept
|
||||
|
||||
NODE = "node"
|
||||
GENERIC_NODE = "generic node"
|
||||
@@ -92,7 +93,8 @@ BuiltinErrors = [str(e) for e in {
|
||||
BuiltinConcepts.CONCEPT_EVAL_ERROR,
|
||||
BuiltinConcepts.CONCEPT_ALREADY_IN_SET,
|
||||
BuiltinConcepts.NOT_A_SET,
|
||||
BuiltinConcepts.WHERE_CLAUSE_FAILED
|
||||
BuiltinConcepts.WHERE_CLAUSE_FAILED,
|
||||
BuiltinConcepts.CHICKEN_AND_EGG
|
||||
}]
|
||||
|
||||
"""
|
||||
|
||||
@@ -4,7 +4,6 @@ import logging
|
||||
import core.ast.nodes
|
||||
from core.ast.nodes import CallNodeConcept, GenericNodeConcept
|
||||
from core.ast.visitors import UnreferencedNamesVisitor
|
||||
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
|
||||
|
||||
@@ -99,6 +98,17 @@ def expect_one(context, return_values, logger=None):
|
||||
False,
|
||||
return_values[0],
|
||||
parents=return_values)
|
||||
else:
|
||||
# test if only one evaluator in error
|
||||
from evaluators.OneErrorEvaluator import OneErrorEvaluator
|
||||
one_error_evaluator = OneErrorEvaluator()
|
||||
reduce_requested = sheerka.ret(context.who, True, sheerka.new(BuiltinConcepts.REDUCE_REQUESTED))
|
||||
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,
|
||||
@@ -210,5 +220,3 @@ def _extract_predicates(sheerka, node, variables_to_include, variables_to_exclud
|
||||
predicates.append(res)
|
||||
|
||||
return predicates
|
||||
|
||||
|
||||
|
||||
+27
-18
@@ -242,24 +242,24 @@ class Concept:
|
||||
def body(self):
|
||||
return self.values[ConceptParts.BODY] if ConceptParts.BODY in self.values else None
|
||||
|
||||
def add_codes(self, codes):
|
||||
"""
|
||||
Gets the ASTs for 'where', 'pre', 'post' and 'body'
|
||||
There ASTs are know when the concept is freshly parsed.
|
||||
So the values are kept in cache.
|
||||
|
||||
For concepts loaded from sdp, these ASTs must be created again
|
||||
TODO : Seems to be a service method. Can be put somewhere else
|
||||
:param codes:
|
||||
:return:
|
||||
"""
|
||||
if codes is None:
|
||||
return
|
||||
|
||||
for key in codes:
|
||||
self.compiled[key] = codes[key]
|
||||
|
||||
return self
|
||||
# def add_codes(self, codes):
|
||||
# """
|
||||
# Gets the ASTs for 'where', 'pre', 'post' and 'body'
|
||||
# There ASTs are know when the concept is freshly parsed.
|
||||
# So the values are kept in cache.
|
||||
#
|
||||
# For concepts loaded from sdp, these ASTs must be created again
|
||||
# TODO : Seems to be a service method. Can be put somewhere else
|
||||
# :param codes:
|
||||
# :return:
|
||||
# """
|
||||
# if codes is None:
|
||||
# return
|
||||
#
|
||||
# for key in codes:
|
||||
# self.compiled[key] = codes[key]
|
||||
#
|
||||
# return self
|
||||
|
||||
def get_digest(self):
|
||||
"""
|
||||
@@ -406,3 +406,12 @@ class DoNotResolve:
|
||||
set concept.compiled[BODY] to DoNotResolve(value)
|
||||
"""
|
||||
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
|
||||
|
||||
@@ -222,6 +222,8 @@ class ExecutionContext:
|
||||
to_str = self.return_value_to_str(r)
|
||||
logger.debug(f"[{self._id:2}]" + self._tab + "-> " + to_str)
|
||||
|
||||
def get_parent(self):
|
||||
return self._parent
|
||||
|
||||
@staticmethod
|
||||
def return_value_to_str(r):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from core.builtin_concepts import BuiltinConcepts
|
||||
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 os
|
||||
|
||||
@@ -28,7 +29,7 @@ class SheerkaDump:
|
||||
defs = self.sheerka.sdp.get(self.sheerka.CONCEPTS_DEFINITIONS_ENTRY)
|
||||
self.sheerka.log.info(defs)
|
||||
|
||||
def dump_desc(self, *concept_names):
|
||||
def dump_desc(self, *concept_names, eval=False):
|
||||
first = True
|
||||
for concept_name in concept_names:
|
||||
if isinstance(concept_name, Concept):
|
||||
@@ -43,13 +44,20 @@ class SheerkaDump:
|
||||
concepts = [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:
|
||||
self.sheerka.log.info("")
|
||||
self.sheerka.log.info(f"name : {c.name}")
|
||||
self.sheerka.log.info(f"bnf : {c.metadata.definition}")
|
||||
self.sheerka.log.info(f"key : {c.key}")
|
||||
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()}")
|
||||
|
||||
if self.sheerka.isaset(c):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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
|
||||
|
||||
CONCEPT_EVALUATION_STEPS = [
|
||||
@@ -13,6 +13,55 @@ class SheerkaEvaluateConcept:
|
||||
self.sheerka = sheerka
|
||||
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):
|
||||
"""
|
||||
Updates the codes of the newly created concept
|
||||
@@ -75,6 +124,15 @@ class SheerkaEvaluateConcept:
|
||||
if isinstance(to_resolve, DoNotResolve):
|
||||
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})"
|
||||
context.log(logger, desc, self.logger_name)
|
||||
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
|
||||
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)
|
||||
sub_context.add_values(return_values=one_r)
|
||||
if one_r.status:
|
||||
@@ -100,7 +159,8 @@ class SheerkaEvaluateConcept:
|
||||
else:
|
||||
error = one_r.value
|
||||
|
||||
return self.sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR,
|
||||
return error if self.sheerka.isinstance(error, BuiltinConcepts.CHICKEN_AND_EGG) \
|
||||
else self.sheerka.new(BuiltinConcepts.CONCEPT_EVAL_ERROR,
|
||||
body=error,
|
||||
concept=current_concept,
|
||||
property_name=current_prop)
|
||||
@@ -168,7 +228,7 @@ class SheerkaEvaluateConcept:
|
||||
else:
|
||||
# Do not send the current concept for the properties
|
||||
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
|
||||
return resolved
|
||||
else:
|
||||
@@ -178,10 +238,10 @@ class SheerkaEvaluateConcept:
|
||||
if part_key in concept.compiled and concept.compiled[part_key] is not None:
|
||||
metadata_ast = concept.compiled[part_key]
|
||||
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
|
||||
else:
|
||||
concept.values[part_key] = resolved
|
||||
concept.values[part_key] = self.get_infinite_recursion_resolution(resolved) or resolved
|
||||
|
||||
# validate where clause
|
||||
if concept.metadata.where is not None:
|
||||
|
||||
+27
-14
@@ -250,10 +250,11 @@ class Sheerka(Concept):
|
||||
self.sdp.save_result(self, execution_context)
|
||||
|
||||
# hack to save valid concept definition
|
||||
if not self.during_restore:
|
||||
if len(ret) == 1 and ret[0].status and self.isinstance(ret[0].value, BuiltinConcepts.NEW_CONCEPT):
|
||||
with open(CONCEPTS_FILE, "a") as f:
|
||||
f.write(text + "\n")
|
||||
# if not self.during_restore:
|
||||
# if len(ret) == 1 and ret[0].status and self.isinstance(ret[0].value, BuiltinConcepts.NEW_CONCEPT):
|
||||
# with open(CONCEPTS_FILE, "a") as f:
|
||||
# f.write(text + "\n")
|
||||
|
||||
return ret
|
||||
|
||||
def execute(self, execution_context, return_values, execution_steps, logger=None):
|
||||
@@ -364,19 +365,26 @@ class Sheerka(Concept):
|
||||
concept_key = str(concept_key)
|
||||
|
||||
# first search in cache
|
||||
result = self.cache_by_key[concept_key] if concept_key in self.cache_by_key else \
|
||||
self.sdp.get_safe(self.CONCEPTS_ENTRY, concept_key)
|
||||
if concept_key in self.cache_by_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
|
||||
|
||||
if isinstance(result, list):
|
||||
if concept_id:
|
||||
# result is a list, but we have the concept_id to discriminate
|
||||
for c in result:
|
||||
if c.id == concept_id:
|
||||
return c
|
||||
else:
|
||||
return result
|
||||
|
||||
metadata = [("key", concept_key), ("id", concept_id)] if concept_id else ("key", concept_key)
|
||||
return self._get_unknown(metadata)
|
||||
@@ -386,10 +394,15 @@ class Sheerka(Concept):
|
||||
return ErrorConcept("Concept id is undefined.")
|
||||
|
||||
# first search in cache
|
||||
result = self.cache_by_id[concept_id] if concept_id in self.cache_by_id else \
|
||||
self.sdp.get_safe(self.CONCEPTS_BY_ID_ENTRY, concept_id)
|
||||
if concept_id in self.cache_by_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):
|
||||
if self.concepts_definitions_cache:
|
||||
|
||||
@@ -6,8 +6,8 @@ from evaluators.BaseEvaluator import OneReturnValueEvaluator
|
||||
from parsers.BaseParser import NotInitializedNode
|
||||
from parsers.ConceptLexerParser import ParsingExpression, ParsingExpressionVisitor
|
||||
from parsers.DefaultParser import DefConceptNode
|
||||
|
||||
from parsers.PythonParser import PythonNode
|
||||
import core.utils
|
||||
|
||||
|
||||
class ConceptOrRuleNameVisitor(ParsingExpressionVisitor):
|
||||
@@ -66,7 +66,7 @@ class AddConceptEvaluator(OneReturnValueEvaluator):
|
||||
setattr(concept.metadata, prop, source)
|
||||
|
||||
# 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):
|
||||
props_found.add(p)
|
||||
|
||||
@@ -108,6 +108,7 @@ class AddConceptEvaluator(OneReturnValueEvaluator):
|
||||
# Case of python code
|
||||
#
|
||||
if isinstance(ret_value.value, ParserResultConcept) and isinstance(ret_value.value.value, PythonNode):
|
||||
if len(concept_name) > 1:
|
||||
python_node = ret_value.value.value
|
||||
as_concept_node = python_to_concept(python_node.ast_)
|
||||
variables = get_names(sheerka, as_concept_node)
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ class BaseTest:
|
||||
|
||||
@staticmethod
|
||||
def pretval(concept, source=None, parser="parsers.name"):
|
||||
"""ParserResult ret_val"""
|
||||
"""ParserResult ret_val (p stands for ParserResult)"""
|
||||
return ReturnValueConcept(
|
||||
"some_name",
|
||||
True,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
|
||||
@@ -80,12 +80,10 @@ class TestSheerkaCreateNewConcept(TestUsingMemoryBasedSheerka):
|
||||
sheerka.cache_by_key = {} # reset the cache
|
||||
loaded = sheerka.get(concept.key)
|
||||
|
||||
assert loaded is not None
|
||||
assert loaded == concept
|
||||
|
||||
# I can also get it by its id
|
||||
loaded = sheerka.sdp.get(sheerka.CONCEPTS_BY_ID_ENTRY, concept.id)
|
||||
assert loaded is not None
|
||||
assert loaded == concept
|
||||
|
||||
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
|
||||
loaded = sheerka.get_by_id(concept.id)
|
||||
|
||||
assert loaded is not None
|
||||
assert loaded == concept
|
||||
|
||||
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")
|
||||
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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
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 tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
|
||||
@@ -369,3 +369,91 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka):
|
||||
evaluated = sheerka.evaluate_concept(self.get_context(sheerka), concept)
|
||||
assert sheerka.isinstance(evaluated, BuiltinConcepts.WHERE_CLAUSE_FAILED)
|
||||
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
|
||||
|
||||
@@ -242,3 +242,41 @@ class TestSheerka(TestUsingFileBasedSheerka):
|
||||
# only test a random one, it will be the same for the others
|
||||
sheerka = self.get_sheerka()
|
||||
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"
|
||||
|
||||
@@ -69,8 +69,7 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
|
||||
return ReturnValueConcept(BaseParser.PREFIX + "some_name", True, ParserResultConcept(value=concept))
|
||||
|
||||
@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),
|
||||
(ReturnValueConcept(BaseParser.PREFIX + "some_name", False, ParserResultConcept(value=DefConceptNode([]))),
|
||||
False),
|
||||
@@ -133,11 +132,17 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
|
||||
|
||||
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)")
|
||||
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):
|
||||
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)
|
||||
|
||||
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 == []
|
||||
|
||||
@@ -103,7 +103,7 @@ class TestAddConceptEvaluator(TestUsingMemoryBasedSheerka):
|
||||
assert context.sheerka.isinstance(result.value, BuiltinConcepts.CONCEPT_EVAL_ERROR)
|
||||
assert result.value.concept == concept_plus
|
||||
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):
|
||||
context = self.get_context()
|
||||
|
||||
@@ -671,3 +671,28 @@ as:
|
||||
|
||||
res = sheerka.evaluate_user_input("eval twenty three")
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user