Fixed #12
Fixed #13
Fixed #14
This commit is contained in:
2023-05-08 17:50:28 +02:00
parent 21a397861a
commit e41094f908
95 changed files with 12168 additions and 260 deletions
View File
+186
View File
@@ -0,0 +1,186 @@
import pytest
from base import BaseTest
from common.global_symbols import NotFound, NotInit
from conftest import NewOntology
from core.BuiltinConcepts import BuiltinConcepts
from core.ErrorContext import ErrorContext
from core.concept import ConceptMetadata
from core.services.SheerkaConceptManager import ConceptAlreadyDefined, ConceptManager
from helpers import get_metadata
class TestConceptManager(BaseTest):
@pytest.fixture()
def service(self, sheerka):
return sheerka.services[ConceptManager.NAME]
def test_i_can_compute_concept_digest(self, service):
"""
Two concepts with the same definition share the same digest
:return:
:rtype:
"""
metadata = get_metadata("foo", "body")
digest = service.compute_metadata_digest(metadata)
assert digest == "21a1c2f420da62f4dc60f600c95b19dd9527b19dd28fd38e17f5c0e28963d176"
another_metadata = get_metadata("foo", "body")
other_digest = service.compute_metadata_digest(another_metadata)
assert digest == other_digest
def test_id_is_not_part_of_the_digest(self, service):
metadata1 = get_metadata("foo", "body", id=1)
metadata2 = get_metadata("foo", "body", id=2)
assert service.compute_metadata_digest(metadata1) == service.compute_metadata_digest(metadata2)
def test_i_can_compute_concept_attributes_based_on_the_metadata(self, service):
compute_all_attrs = service.compute_all_attrs
m1 = get_metadata("foo")
assert compute_all_attrs(m1.variables) == ('#where#', '#pre#', '#post#', '#body#', '#ret#')
m2 = get_metadata("bar", variables=[("var1", None), ("var2", None)])
assert compute_all_attrs(m2.variables) == ('#where#', '#pre#', '#post#', '#body#', '#ret#', 'var1', 'var2')
@pytest.mark.parametrize("definition, variables, expected", [
("foo", [], "foo"),
("foo(bar)", [], "foo ( bar )"),
("foo a", ["a"], "foo __var__0"),
("a foo b", ["a", "b"], "__var__0 foo __var__1"),
("a foo b", ["b", "a"], "__var__1 foo __var__0"),
("foo", ["foo"], "foo"),
("foo a", ["foo"], "__var__0 a"),
("foo a b", ["a"], "foo __var__0 b"),
("'foo'", [], "'foo'"),
("my name is a", ["a"], "my name is __var__0"),
("a b c d", ["b", "c"], "a __var__0 __var__1 d"),
("a 'b c' d", ["b", "c"], "a 'b c' d"),
("a | b", ["a", "b"], "__var__0 | __var__1"),
("a b a c", ["a", "b"], "__var__0 __var__1 __var__0 c"),
("a b a c", ["b", "a"], "__var__1 __var__0 __var__1 c"),
("def concept a", ["a"], "def concept __var__0"),
])
def test_i_can_create_concept_key(self, service, definition, variables, expected):
expanded_variables = tuple((v, NotInit) for v in variables)
key = service.create_concept_key(definition, None, expanded_variables)
assert key == expected
def test_the_key_is_created_from_the_definition_if_it_is_set(self, service):
assert service.create_concept_key("from name", "from definition", None) == "from definition"
assert service.create_concept_key("from name", None, None) == "from name"
def test_i_can_define_a_new_concept(self, context, service):
with NewOntology(context, "test_i_can_define_a_new_concept"):
res = service.define_new_concept(context, "name", body="body")
assert res.status is True
metadata = res.value.metadata
assert isinstance(metadata, ConceptMetadata)
assert metadata.id == "1001"
assert metadata.name == "name"
assert metadata.key == "name"
assert metadata.body == "body"
assert metadata.digest == "eb0620bd4a317af8a403c0ae1e185a528f9b58f8b0878d990e62278f89cf10d5"
assert metadata.all_attrs == ('#where#', '#pre#', '#post#', '#body#', '#ret#')
# is sorted in db
om = context.sheerka.om
assert om.get(ConceptManager.CONCEPTS_BY_ID_ENTRY, metadata.id) == metadata
assert om.get(ConceptManager.CONCEPTS_BY_NAME_ENTRY, metadata.name) == metadata
assert om.get(ConceptManager.CONCEPTS_BY_KEY_ENTRY, metadata.key) == metadata
assert om.get(ConceptManager.CONCEPTS_BY_HASH_ENTRY, metadata.digest) == metadata
def test_i_cannot_create_the_same_concept_twice(self, context, service):
with NewOntology(context, "test_i_cannot_create_the_same_concept_twice"):
res = service.define_new_concept(context, "name", body="body")
assert res.status
res = service.define_new_concept(context, "name", body="body")
assert not res.status
assert isinstance(res.value, ErrorContext)
assert isinstance(res.value.value, ConceptAlreadyDefined)
def test_i_can_add_the_same_concept_on_different_ontologies(self, context, service):
with NewOntology(context, "test_i_can_add_the_same_concept_on_different_ontologies"):
res = service.define_new_concept(context, "name", body="body")
assert res.status
sheerka = context.sheerka
om = sheerka.om
om.push_ontology("my_new_ontology")
res = service.define_new_concept(context, "name", body="body")
assert res.status is True
def test_i_can_get_a_newly_created_concept(self, context, service):
with NewOntology(context, "test_i_can_get_a_newly_created_concept"):
res = service.define_new_concept(context, "name", body="body")
assert res.status
metadata = res.value.metadata
assert service.get_by_id(metadata.id).id == metadata.id
assert service.get_by_name(metadata.name).name == metadata.name
assert service.get_by_key(metadata.key).key == metadata.key
def test_i_can_instantiate_a_new_concept_by_its_name(self, context, service):
with NewOntology(context, "test_i_can_instantiate_a_new_concept_by_its_name"):
res = service.define_new_concept(context, "foo", variables=[("var1", None), ("var2", None)])
assert res.status
foo = service.newn("foo", var1="value1", var2="value2")
assert foo.id == "1001"
assert foo.key == "foo"
assert foo.name == "foo"
assert foo.str_id == "c:#1001:"
assert foo.var1 == "value1"
assert foo.var2 == "value2"
def test_i_can_instantiate_a_new_concept_by_its_id(self, context, service):
with NewOntology(context, "test_i_can_instantiate_a_new_concept_by_its_id"):
res = service.define_new_concept(context, "foo", variables=[("var1", None), ("var2", None)])
assert res.status
foo = service.newi("1001", var1="value1", var2="value2")
assert foo.id == "1001"
assert foo.key == "foo"
assert foo.name == "foo"
assert foo.str_id == "c:#1001:"
assert foo.var1 == "value1"
assert foo.var2 == "value2"
def test_i_cannot_instantiate_a_concept_which_does_not_exist(self, context, service):
foo = service.newn("foo", var1="value1", var2="value2")
assert foo.key == BuiltinConcepts.UNKNOWN_CONCEPT
assert foo.requested_name == "foo"
foo = service.newi("1001", var1="value1", var2="value2")
assert foo.key == BuiltinConcepts.UNKNOWN_CONCEPT
assert foo.requested_id == "1001"
def test_i_can_instantiate_by_name_when_multiple_results(self, context, service):
with NewOntology(context, "test_i_can_instantiate_by_name_when_multiple_results"):
service.define_new_concept(context, "foo", body="body1")
service.define_new_concept(context, "foo", body="body2")
concepts = service.newn("foo")
assert len(concepts) == 2
assert concepts[0].id == "1001"
assert concepts[0].get_metadata().body == "body1"
assert concepts[1].id == "1002"
assert concepts[1].get_metadata().body == "body2"
def test_concepts_are_removed_when_ontology_is_popped(self, context, service):
context.sheerka.om.push_ontology("new ontology")
res = service.define_new_concept(context, "foo", body="body")
assert service.get_by_id(res.value.metadata.id) is not NotFound
context.sheerka.om.pop_ontology(context)
assert service.get_by_id(res.value.metadata.id) is NotFound
+379
View File
@@ -0,0 +1,379 @@
from typing import Callable
import pytest
from base import BaseTest
from core.BuiltinConcepts import BuiltinConcepts
from core.ExecutionContext import ExecutionContext, ExecutionContextActions
from core.ReturnValue import ReturnValue
from core.services.SheerkaEngine import SheerkaEngine
from evaluators.CreateParserInput import CreateParserInput
from evaluators.base_evaluator import AllReturnValuesEvaluator, BaseEvaluator, EvaluatorEvalResult, \
EvaluatorMatchResult, \
OneReturnValueEvaluator
from helpers import _rvc
ALL_STEPS = [
ExecutionContextActions.BEFORE_PARSING,
ExecutionContextActions.PARSING,
ExecutionContextActions.AFTER_PARSING,
ExecutionContextActions.BEFORE_EVALUATION,
ExecutionContextActions.EVALUATION,
ExecutionContextActions.AFTER_EVALUATION
]
class OneReturnValueEvaluatorForTesting(OneReturnValueEvaluator):
def __init__(self, name,
step: ExecutionContextActions,
priority: int,
enabled=True,
match: bool | Callable = True,
match_context=None,
eval_result: list[ReturnValue] = None,
eval_eaten: list[ReturnValue] = None):
super().__init__(name, step, priority, enabled)
self.matches_delegate = match
self.matches_context = match_context
self.eval_result = eval_result
self.eval_eaten = eval_eaten
def matches(self, context: ExecutionContext, return_value: ReturnValue) -> EvaluatorMatchResult:
# if status is a bool, use it
# otherwise, it's a delegate, so apply to return_value
status = self.matches_delegate if \
isinstance(self.matches_delegate, bool) else \
self.matches_delegate(return_value)
return EvaluatorMatchResult(status, self.matches_context)
def eval(self, context: ExecutionContext,
evaluation_context: object,
return_value: ReturnValue) -> EvaluatorEvalResult:
# make sure to correctly set up the parent when the return value is modified
if self.eval_result:
for ret_val in self.eval_result:
if ret_val != return_value:
ret_val.parents = [return_value]
return EvaluatorEvalResult(self.eval_result, self.eval_eaten or [return_value])
class AllReturnValuesEvaluatorForTesting(AllReturnValuesEvaluator):
def __init__(self, name,
step: ExecutionContextActions,
priority: int,
enabled=True,
match: bool | Callable = True,
match_context=None,
eval_result: list[ReturnValue] = None,
eval_eaten: list[ReturnValue] = None):
super().__init__(name, step, priority, enabled)
self.matches_delegate = match
self.matches_context = match_context
self.eval_result = eval_result
self.eval_eaten = eval_eaten
def matches(self, context: ExecutionContext, return_values: list[ReturnValue]) -> EvaluatorMatchResult:
# if status is a bool, use it
# otherwise, it's a delegate, so apply to return_value
status = self.matches_delegate if \
isinstance(self.matches_delegate, bool) else \
self.matches_delegate(return_values)
return EvaluatorMatchResult(status, self.matches_context)
def eval(self, context: ExecutionContext,
evaluation_context: object,
return_values: list[ReturnValue]) -> EvaluatorEvalResult:
# make sure to correctly set up the parent when the return value is modified
if self.eval_result:
for ret_val in self.eval_result:
ret_val.parents = return_values
return EvaluatorEvalResult(self.eval_result, self.eval_eaten or return_values)
class TestSheerkaEngine(BaseTest):
@pytest.fixture()
def service(self, sheerka):
return SheerkaEngine(sheerka)
def test_i_can_compute_execution_plan(self, service):
assert service.compute_execution_plan([]) == {}
e1 = BaseEvaluator("eval1", ExecutionContextActions.BEFORE_EVALUATION, 5)
e2 = BaseEvaluator("eval2", ExecutionContextActions.BEFORE_EVALUATION, 5)
e3 = BaseEvaluator("eval3", ExecutionContextActions.BEFORE_EVALUATION, 10)
e4 = BaseEvaluator("eval4", ExecutionContextActions.EVALUATION, 10)
e5 = BaseEvaluator("eval5", ExecutionContextActions.AFTER_EVALUATION, 10, enabled=False)
res = service.compute_execution_plan([e1, e2, e3, e4, e5])
assert res == {ExecutionContextActions.BEFORE_EVALUATION: {5: [e1, e2], 10: [e3]},
ExecutionContextActions.EVALUATION: {10: [e4]}}
def test_i_can_call_execute(self, sheerka, context, service):
service.execution_plan = {ExecutionContextActions.BEFORE_EVALUATION: {50: [CreateParserInput()]}}
start = [ReturnValue("TestSheerkaEngine", True, sheerka.newn(BuiltinConcepts.USER_INPUT, command="1 + 1"))]
ret = service.execute(context, start, [ExecutionContextActions.BEFORE_EVALUATION])
assert len(ret) == 1
ret = ret[0]
assert isinstance(ret, ReturnValue)
assert ret.who == CreateParserInput.NAME
assert ret.status is True
assert ret.parents == start
def test_that_return_values_is_unchanged_when_no_evaluator(self, context, service):
service.execution_plan = {}
start = [_rvc("foo")]
ret = service.execute(context, start, [ExecutionContextActions.EVALUATION])
assert ret == start
def test_steps_are_executed_in_correct_order(self, context, service):
# properly init the service
_ = OneReturnValueEvaluatorForTesting
evaluators = [
_("eval1", ExecutionContextActions.AFTER_PARSING, 21, match=False),
_("eval2", ExecutionContextActions.BEFORE_EVALUATION, 5, match=False),
_("eval3", ExecutionContextActions.AFTER_EVALUATION, 12, match=False),
_("eval4", ExecutionContextActions.EVALUATION, 99, match=False),
_("eval5", ExecutionContextActions.BEFORE_PARSING, 5, match=False),
_("eval6", ExecutionContextActions.PARSING, 25, match=False),
]
service.execution_plan = service.compute_execution_plan(evaluators)
# init test variables
start = [_rvc("foo")]
service.execute(context, start, ALL_STEPS)
# to check what happened, look at the execution context children
executed_steps = [ec.action_context["step"] for ec in context.get_children(level=1)]
assert executed_steps == ALL_STEPS
def test_higher_priority_evaluators_are_executed_first(self, context, service):
# properly init the service
_ = OneReturnValueEvaluatorForTesting
evaluators = [
_("eval1", ExecutionContextActions.EVALUATION, 20, match=False),
_("eval2", ExecutionContextActions.EVALUATION, 5, match=False),
_("eval3", ExecutionContextActions.EVALUATION, 20, match=False),
_("eval4", ExecutionContextActions.EVALUATION, 99, match=False),
]
service.execution_plan = service.compute_execution_plan(evaluators)
start = [_rvc("foo")]
service.execute(context, start, [ExecutionContextActions.EVALUATION])
# to check what happened, look at the execution context children
evaluators_executed = [ec.action_context["evaluator"] for ec in context.get_children() if
"evaluator" in ec.action_context]
assert evaluators_executed == ["eval4", "eval1", "eval3", "eval2"]
def test_evaluation_loop_stops_when_no_modification(self, context, service):
rv_foo, rv_bar = _rvc("foo"), _rvc("bar") # rv => ReturnValue
# properly init the service
_ = OneReturnValueEvaluatorForTesting
evaluators = [
_("eval1",
ExecutionContextActions.EVALUATION,
20,
match=lambda r: context.sheerka.isinstance(r.value, "foo"),
eval_result=[rv_bar])
]
service.execution_plan = service.compute_execution_plan(evaluators)
start = [rv_foo]
service.execute(context, start, [ExecutionContextActions.EVALUATION])
children = [ec for ec in context.get_children() if ec.action == ExecutionContextActions.EVALUATING_ITERATION]
assert len(children) == 2
def test_eval_is_not_called_if_match_fails_for_one_return(self, context, service):
# properly init the service
_ = OneReturnValueEvaluatorForTesting
evaluators = [
_("eval1",
ExecutionContextActions.EVALUATION,
20,
match=lambda r: context.sheerka.isinstance(r.value, "foo"),
eval_result=[_rvc("bar")])
]
service.execution_plan = service.compute_execution_plan(evaluators)
start = [_rvc("baz")]
res = service.execute(context, start, [ExecutionContextActions.EVALUATION])
assert res == start
# check what happen in details
exec_context = next(filter(lambda ec: "evaluator" in ec.action_context, context.get_children()))
evaluation_trace = exec_context.values["evaluation"]
assert evaluation_trace == [{"item": start[0], "match": False}]
def test_eval_is_called_if_match_succeed_for_one_return(self, context, service):
# properly init the service
_ = OneReturnValueEvaluatorForTesting
evaluators = [
_("eval1",
ExecutionContextActions.EVALUATION,
20,
match=lambda r: context.sheerka.isinstance(r.value, "foo"),
eval_result=[_rvc("bar")])
]
service.execution_plan = service.compute_execution_plan(evaluators)
start = [_rvc("foo")]
res = service.execute(context, start, [ExecutionContextActions.EVALUATION])
assert res == [_rvc("bar")]
assert res[0].parents == start
# check what happen in details
exec_context = next(filter(lambda ec: "evaluator" in ec.action_context, context.get_children()))
evaluation_trace = exec_context.values["evaluation"]
assert evaluation_trace == [{"item": start[0], "match": True, "new": res, "eaten": start}]
def test_all_item_are_processed_during_one_return(self, context, service):
rv_foo, rv_bar, rv_baz, rv_qux = _rvc("foo"), _rvc("bar"), _rvc("baz"), _rvc("qux") # rv => ReturnValue
# properly init the service
_ = OneReturnValueEvaluatorForTesting
evaluators = [
_("eval1",
ExecutionContextActions.EVALUATION,
20,
match=lambda r: context.sheerka.isinstance(r.value, "foo"),
eval_result=[rv_qux])
]
service.execution_plan = service.compute_execution_plan(evaluators)
start = [rv_bar, rv_foo, rv_baz]
res = service.execute(context, start, [ExecutionContextActions.EVALUATION])
assert res == [rv_bar, rv_qux, rv_baz] # We must keep the order ! rv_qux replaces rv_foo
assert res[0].parents is None
assert res[1].parents == [rv_foo]
assert res[2].parents is None
# check what happen in details
exec_context = next(filter(lambda ec: "evaluator" in ec.action_context, context.get_children()))
evaluation_trace = exec_context.values["evaluation"]
assert evaluation_trace == [{"item": rv_bar, "match": False},
{"item": rv_foo, "match": True, "new": [rv_qux], "eaten": [rv_foo]},
{"item": rv_baz, "match": False}]
def test_evaluators_with_the_same_priority_do_not_compete_with_each_other_one_return(self, context, service):
rv_foo, rv_bar, rv_baz, rv_qux = _rvc("foo"), _rvc("bar"), _rvc("baz"), _rvc("qux") # rv => ReturnValue
# properly init the service
# both evaluator want to eat 'foo'
_ = OneReturnValueEvaluatorForTesting
evaluators = [
_("eval1",
ExecutionContextActions.EVALUATION,
20,
match=lambda r: context.sheerka.isinstance(r.value, "foo"),
eval_result=[rv_bar]),
_("eval2",
ExecutionContextActions.EVALUATION,
20,
match=lambda r: context.sheerka.isinstance(r.value, "foo"),
eval_result=[rv_baz])
]
service.execution_plan = service.compute_execution_plan(evaluators)
start = [rv_qux, rv_foo, rv_qux]
res = service.execute(context, start, [ExecutionContextActions.EVALUATION])
assert res == [rv_qux, rv_bar, rv_baz, rv_qux] # they both eat it !
assert res[1].parents == [rv_foo]
assert res[2].parents == [rv_foo]
def test_evaluators_with_higher_priority_take_precedence_one_return(self, context, service):
rv_foo, rv_bar, rv_baz = _rvc("foo"), _rvc("bar"), _rvc("baz") # rv => ReturnValue
# properly init the service
# both evaluator want to eat 'foo'
_ = OneReturnValueEvaluatorForTesting
evaluators = [
_("eval1",
ExecutionContextActions.EVALUATION,
20,
match=lambda r: context.sheerka.isinstance(r.value, "foo"),
eval_result=[rv_bar]),
_("eval2",
ExecutionContextActions.EVALUATION,
30,
match=lambda r: context.sheerka.isinstance(r.value, "foo"),
eval_result=[rv_baz])
]
service.execution_plan = service.compute_execution_plan(evaluators)
start = [rv_foo]
res = service.execute(context, start, [ExecutionContextActions.EVALUATION])
assert res == [rv_baz]
assert res[0].parents == start
def test_evaluator_matches_is_called_before_eval_for_all_return(self, context, service):
# properly init the service
_ = AllReturnValuesEvaluatorForTesting
evaluators = [
_("eval1",
ExecutionContextActions.EVALUATION,
20,
match=lambda r: context.sheerka.isinstance(r[0].value, "foo"),
eval_result=[_rvc("bar")])
]
service.execution_plan = service.compute_execution_plan(evaluators)
start = [_rvc("baz")]
res = service.execute(context, start, [ExecutionContextActions.EVALUATION])
assert res == start
start = [_rvc("foo")]
res = service.execute(context, start, [ExecutionContextActions.EVALUATION])
assert res == [_rvc("bar")]
assert res[0].parents == start
def test_eval_is_not_call_if_match_fails_for_all_return(self, context, service):
rv_foo, rv_bar, rv_baz = _rvc("foo"), _rvc("bar"), _rvc("baz") # rv => ReturnValue
# properly init the service
_ = AllReturnValuesEvaluatorForTesting
evaluators = [
_("eval1",
ExecutionContextActions.EVALUATION,
20,
match=lambda lst: context.sheerka.isinstance(lst[0].value, "foo"),
eval_result=[rv_bar])
]
service.execution_plan = service.compute_execution_plan(evaluators)
start = [rv_baz, rv_foo] # foo is not the first in the list
res = service.execute(context, start, [ExecutionContextActions.EVALUATION])
assert res == start
# check what happen in details
exec_context = next(filter(lambda ec: "evaluator" in ec.action_context, context.get_children()))
evaluation_trace = exec_context.values["evaluation"]
assert evaluation_trace == {"match": False}
def test_eval_is_called_if_match_succeed_for_all_return(self, context, service):
rv_foo, rv_bar, rv_baz = _rvc("foo"), _rvc("bar"), _rvc("baz") # rv => ReturnValue
# properly init the service
_ = AllReturnValuesEvaluatorForTesting
evaluators = [
_("eval1",
ExecutionContextActions.EVALUATION,
20,
match=lambda lst: context.sheerka.isinstance(lst[0].value, "foo"),
eval_result=[rv_bar])
]
service.execution_plan = service.compute_execution_plan(evaluators)
start = [rv_foo, rv_baz]
res = service.execute(context, start, [ExecutionContextActions.EVALUATION])
assert res == [rv_bar]
assert res[0].parents == start
children = list(context.get_children())
# check what happen in details
exec_context = next(filter(lambda ec: "evaluator" in ec.action_context, context.get_children()))
evaluation_trace = exec_context.values["evaluation"]
assert evaluation_trace == {"match": True, "new": res, "eaten": start}