Files
Sheerka/tests/services/test_ConceptEvaluator.py

583 lines
30 KiB
Python

import pytest
from base import BaseTest
from common.global_symbols import NotInit
from conftest import NewOntology
from core.BuiltinConcepts import BuiltinConcepts
from core.concept import ConceptDefaultProps
from core.python_fragment import PythonFragment
from helpers import define_new_concept, get_concept, get_concepts, get_metadata
from services.SheerkaConceptEvaluator import ConceptEvaluator, InfiniteRecursion, TooManyErrors
from services.SheerkaPython import ObjectRef
class TestConceptManager(BaseTest):
@pytest.fixture()
def service(self, sheerka) -> ConceptEvaluator:
return sheerka.services[ConceptEvaluator.NAME]
def test_i_can_build_concept(self, context, service):
metadata = get_metadata(
name="foo",
where="isinstance(x, Concept)",
pre="in_context(IS_QUESTION)",
body="one + a",
post="'post parameter'",
ret="self",
variables=(("a", "1"), ("b", "NotInit"))
)
compiled = service._build_attributes(context, metadata)
pf = getattr(compiled, ConceptDefaultProps.WHERE)
assert isinstance(pf, PythonFragment)
assert pf.source_code == metadata.where
pf = getattr(compiled, ConceptDefaultProps.PRE)
assert isinstance(pf, PythonFragment)
assert pf.source_code == metadata.pre
pf = getattr(compiled, ConceptDefaultProps.BODY)
assert isinstance(pf, PythonFragment)
assert pf.source_code == metadata.body
pf = getattr(compiled, ConceptDefaultProps.POST)
assert isinstance(pf, PythonFragment)
assert pf.source_code == metadata.post
pf = getattr(compiled, ConceptDefaultProps.RET)
assert isinstance(pf, PythonFragment)
assert pf.source_code == metadata.ret
pf = getattr(compiled, "a")
assert isinstance(pf, PythonFragment)
assert pf.source_code == metadata.variables[0][1]
pf = getattr(compiled, "b")
assert isinstance(pf, PythonFragment)
assert pf.source_code == metadata.variables[1][1]
def test_i_can_manage_when_no_source_code(self, context, service):
metadata = get_metadata(name="foo")
compiled = service._build_attributes(context, metadata)
assert getattr(compiled, ConceptDefaultProps.WHERE) is None
assert getattr(compiled, ConceptDefaultProps.PRE) is None
assert getattr(compiled, ConceptDefaultProps.BODY) is None
assert getattr(compiled, ConceptDefaultProps.POST) is None
assert getattr(compiled, ConceptDefaultProps.RET) is None
def test_i_can_detect_when_requested_names_are_concept_variables(self, context, service):
metadata = get_metadata(
name="foo",
body="one + a",
variables=(("a", "1"), ("b", "NotInit")))
compiled = service._build_attributes(context, metadata)
pf = getattr(compiled, ConceptDefaultProps.BODY)
assert isinstance(pf, PythonFragment)
assert pf.namespace == {"a": ObjectRef("self", "a"),
"b": ObjectRef("self", "b")}
def test_i_can_manage_parsing_errors(self, context, service):
metadata = get_metadata(
name="foo",
where="isinstance(a, int)", # ok
body="one + ", # not ok
variables=("a",)) # ok
compiled = service._build_attributes(context, metadata)
pf = getattr(compiled, ConceptDefaultProps.WHERE)
assert isinstance(pf, PythonFragment)
assert pf.source_code == metadata.where
pf = getattr(compiled, "a")
assert isinstance(pf, PythonFragment)
assert pf.source_code == "NotInit"
error = getattr(compiled, ConceptDefaultProps.BODY)
assert isinstance(error, TooManyErrors)
def test_i_can_eval_concept_attributes(self, context, service):
with NewOntology(context, "test_i_can_eval_concept_attributes"):
foo_metadata = get_metadata(name="foo",
where="isinstance(a, int)",
pre="True",
body="2 + a",
post="'post parameter'",
ret="self",
variables=(("a", "1"), ("b", "NotInit"), ("c", "1 > 2")))
foo = define_new_concept(context, foo_metadata)
res = service.evaluate_concept(context, foo)
assert context.sheerka.isinstance(res, foo)
assert res.get_value("a") == 1
assert res.get_value("b") == NotInit
assert res.get_value("c") is False
assert res.get_value(ConceptDefaultProps.WHERE) is True
assert res.get_value(ConceptDefaultProps.PRE) is True
assert res.get_value(ConceptDefaultProps.BODY) == 3
assert res.get_value(ConceptDefaultProps.POST) == "post parameter"
assert res.get_value(ConceptDefaultProps.RET) == res
assert res.get_runtime_info().is_evaluated is True
assert res.get_runtime_info().error is None
assert ConceptDefaultProps.PRE in res.get_runtime_info().info
assert ConceptDefaultProps.WHERE in res.get_runtime_info().info
def test_i_can_can_evaluate_simple_concept(self, context, service):
with NewOntology(context, "test_i_can_eval_concept_attributes"):
foo_metadata = get_metadata(name="foo",
body="1")
foo = define_new_concept(context, foo_metadata)
res = service.evaluate_concept(context, foo)
assert context.sheerka.isinstance(res, foo)
assert res.body == 1
assert res.get_runtime_info().is_evaluated is True
assert res.get_runtime_info().error is None
def test_i_cannot_evaluate_an_invalid_concept(self, context, service):
with NewOntology(context, "test_i_cannot_evaluate_an_invalid_concept"):
foo_metadata = get_metadata(name="foo",
where="isinstance(a, int", # ok
body="one + ", # not ok
variables=("a",))
foo = define_new_concept(context, foo_metadata)
res = service.evaluate_concept(context, foo)
assert context.sheerka.isinstance(res, BuiltinConcepts.INVALID_CONCEPT)
assert ConceptDefaultProps.BODY in res.get_value("reason")
assert ConceptDefaultProps.WHERE in res.get_value("reason")
assert foo.get_runtime_info().is_evaluated is True
assert foo.get_runtime_info().error == res
def test_i_can_manage_runtime_errors(self, context, service):
with NewOntology(context, "test_i_can_manage_runtime_errors"):
foo_metadata = get_metadata(name="foo",
where="isinstance(a, int)", # ok
body="one + a", # not ok
variables=(("a", "1"),))
foo = define_new_concept(context, foo_metadata)
res = service.evaluate_concept(context, foo)
assert context.sheerka.isinstance(res, BuiltinConcepts.EVALUATION_ERROR)
assert ConceptDefaultProps.BODY in res.get_value("reason")
assert foo.get_runtime_info().is_evaluated is True
assert ConceptDefaultProps.BODY in foo.get_runtime_info().error
assert foo.get_runtime_info().error["#body#"].get_error_msg() == "name 'one' is not defined"
def test_i_do_not_override_initialisation_values_when_i_evaluate(self, context, service):
with NewOntology(context, "test_i_do_not_override_initialisation_values_when_i_evaluate"):
foo_metadata = get_metadata(name="foo",
variables=(("a", "1"), ("b", "'hello world'")))
define_new_concept(context, foo_metadata)
foo_instance = context.sheerka.new("foo", a=42, b="my value")
assert foo_instance.a == 42
assert foo_instance.b == "my value"
foo_evaluated = service.evaluate_concept(context, foo_instance)
assert foo_evaluated.a == 42
assert foo_evaluated.b == "my value"
def test_i_can_reference_other_concept(self, context, service):
with NewOntology(context, "test_i_can_reference_other_concept"):
foo, bar = get_concepts(context, "foo", get_concept("bar", body="foo"), use_sheerka=True)
res = service.evaluate_concept(context, bar)
assert context.sheerka.isinstance(res, bar)
assert bar.body == foo
def test_i_can_reference_other_concept_with_body(self, context, service):
with NewOntology(context, "test_i_can_reference_other_concept_with_body"):
foo, bar = get_concepts(context,
get_concept("foo", body="1"),
get_concept("bar", body="foo"),
use_sheerka=True)
res = service.evaluate_concept(context, bar)
assert context.sheerka.isinstance(res, bar)
assert context.sheerka.isinstance(bar.body, foo)
assert bar.body.body == 1
def test_i_can_eval_concept_of_concept(self, context, service):
with NewOntology(context, "test_i_can_eval_concept_of_concept"):
foo, bar, baz, qux = get_concepts(context,
get_concept("foo", body="1"),
get_concept("bar", body="foo"),
get_concept("baz", body="bar"),
get_concept("qux", body="baz"),
use_sheerka=True)
res = service.evaluate_concept(context, qux)
assert context.sheerka.isinstance(res, qux)
assert context.sheerka.isinstance(qux.body, baz)
assert context.sheerka.isinstance(qux.body.body, bar)
assert context.sheerka.isinstance(qux.body.body.body, foo)
assert context.sheerka.objvalue(qux) == 1
def test_concept_variables_precede_global_concepts(self, context, service):
# In this test, there is a variable named "foo"
# Its value is the concept 'bar'
# So when the body is evaluated, we expected Concept(bar), not Concept(foo)
with NewOntology(context, "test_concept_variables_precede_global_concepts"):
foo, bar, baz = get_concepts(context,
get_concept("foo"),
get_concept("bar"),
get_concept("baz", body="foo", variables=(("foo", "bar"),)),
use_sheerka=True)
res = service.evaluate_concept(context, baz)
assert context.sheerka.isinstance(res, baz)
assert context.sheerka.isinstance(res.body, bar)
def test_concept_variables_precede_global_concept_during_computation(self, context, service):
# In this test, there is a variable named "foo" and a concept also named "foo"
# When evaluated, foo + 1 must use the variable 'foo', not the Concept("foo")
with NewOntology(context, "test_concept_variables_precede_global_concepts"):
foo, bar = get_concepts(context,
get_concept("foo", body="2"),
get_concept("bar", body="foo + 1", variables=(("foo", "1"),)),
use_sheerka=True)
res = service.evaluate_concept(context, bar)
assert context.sheerka.isinstance(res, bar)
assert context.sheerka.objvalue(res) == 2
def test_i_can_evaluate_concept_when_variables_reference_others_concepts_with_body(self, context, service):
with NewOntology(context, "test_i_can_evaluate_concept_when_variables_reference_others_concepts_with_body"):
foo, bar, baz = get_concepts(context,
get_concept("foo", body="1"),
get_concept("bar", body="2"),
get_concept("baz", body="a + b", variables=(("a", "foo"), ("b", "bar"))),
use_sheerka=True)
res = service.evaluate_concept(context, baz)
assert context.sheerka.isinstance(res, baz)
assert res.body == 3
def test_i_can_evaluate_concept_when_variable_is_a_concept_token(self, context, service):
with NewOntology(context, "test_i_can_evaluate_concept_when_variable_is_a_concept_token"):
foo, bar = get_concepts(context, "foo", get_concept("bar", body="c:foo:"), use_sheerka=True)
res = service.evaluate_concept(context, bar)
assert context.sheerka.isinstance(res, bar)
assert bar.body == foo
def test_i_can_evaluate_when_concept_attribute_is_referenced(self, context, service):
with NewOntology(context, "test_i_can_evaluate_when_concept_attribute_is_referenced"):
foo, bar = get_concepts(context,
get_concept("foo", variables=(("var", "'I am var'"),)),
get_concept("bar", body="foo.var"),
use_sheerka=True)
res = service.evaluate_concept(context, bar)
assert context.sheerka.isinstance(res, bar)
assert bar.body == 'I am var'
@pytest.mark.skip("I cannot parse complex concept")
def test_i_can_evaluate_when_body_is_a_complex_concept(self, context, service):
with NewOntology(context, "test_i_can_evaluate_concept_when_variable_is_a_concept_token"):
add, plus = get_concepts(context,
get_concept("add", body="a plus b", variables=("a", "b")),
get_concept("plus", body="x + y", variables=("x", "y")),
use_sheerka=True)
add_instance = context.sheerka.new(add, a=1, b=2)
add_instance.get_runtime_info().is_evaluated = False # little hack, before
res = service.evaluate_concept(context, add_instance)
assert context.sheerka.isinstance(res, add)
assert context.sheerka.isinstance(add_instance.body, plus)
assert context.sheerka.objvalue(add_instance) == 3
@pytest.mark.skip("I cannot parse complex concept")
def test_i_can_evaluate_when_body_is_a_complex_concept_and_same_variables_names(self, context, service):
with NewOntology(context, "test_i_can_evaluate_when_body_is_a_complex_concept_and_same_variables_names"):
add, plus = get_concepts(context,
get_concept("add", body="a plus b", variables=("a", "b")),
get_concept("plus", body="a + b", variables=("a", "b")),
use_sheerka=True)
add_instance = context.sheerka.new(add, a=1, b=2)
res = service.evaluate_concept(context, add_instance)
assert context.sheerka.isinstance(res, add)
assert context.sheerka.isinstance(add_instance.body, plus)
assert context.sheerka.objvalue(add_instance) == 3
def test_body_is_not_evaluated_if_where_clause_failed(self, context, service):
foo = get_concept("foo", body="'hello world'", where="False")
res = service.evaluate_concept(context, foo)
assert context.sheerka.isinstance(res, BuiltinConcepts.EVALUATION_ERROR)
assert res.concept.get_value(ConceptDefaultProps.WHERE) is False
assert ConceptDefaultProps.WHERE in res.concept.get_runtime_info().error
assert res.concept.body is NotInit
def test_body_is_not_evaluated_if_pre_clause_failed(self, context, service):
foo = get_concept("foo", body="'hello world'", pre="False")
res = service.evaluate_concept(context, foo)
assert context.sheerka.isinstance(res, BuiltinConcepts.EVALUATION_ERROR)
assert res.concept.get_value(ConceptDefaultProps.PRE) is False
assert ConceptDefaultProps.PRE in res.concept.get_runtime_info().error
assert res.concept.body is NotInit
def test_i_can_evaluate_when_variable_returns_multiple_choices(self, context, service):
with NewOntology(context, "test_i_can_evaluate_when_variable_returns_multiple_choices"):
one_int, one_str, inc = get_concepts(context,
get_concept("one", body="'one'"),
get_concept("one", body="1"),
get_concept("inc", body="a + 2", variables=(("a", "one"),)),
use_sheerka=True)
res = service.evaluate_concept(context, inc)
assert context.sheerka.isinstance(res, inc)
assert res.body == 3
def test_i_can_evaluate_when_variables_refer_to_complex_concept_synonyms(self, context, service):
with NewOntology(context, "test_i_can_evaluate_when_variables_refer_to_complex_concept_synonyms"):
one_int, one_str, inc = get_concepts(context,
get_concept("one", body="'one'"),
get_concept("one", body="1"),
get_concept("inc", body="a + 2", variables=(("a", "one + 1"),)),
use_sheerka=True)
res = service.evaluate_concept(context, inc)
assert context.sheerka.isinstance(res, inc)
assert res.body == 4
def test_i_can_use_where_clause_on_attr_value(self, context, service):
with NewOntology(context, "test_i_can_use_where_clause_on_attr_value"):
one_2, one_1, foo = get_concepts(context,
get_concept("one", body="2"),
get_concept("one", body="1"),
get_concept("foo",
body="a + 2",
where="a == 1",
variables=(("a", "one"),)),
use_sheerka=True)
res = service.evaluate_concept(context, foo)
assert context.sheerka.isinstance(res, foo)
assert res.body == 3
def test_i_can_use_where_clause_on_complex_attr_value(self, context, service):
with NewOntology(context, "test_i_can_use_where_clause_on_complex_attr_value"):
one_2, one_1, foo = get_concepts(context,
get_concept("one", body="2"),
get_concept("one", body="1"),
get_concept("foo",
body="a + 2",
where="a == 2",
variables=(("a", "one + 1"),)),
use_sheerka=True)
res = service.evaluate_concept(context, foo)
assert context.sheerka.isinstance(res, foo)
assert res.body == 4
def test_i_can_use_complex_where_clause(self, context, service):
# a 'where' clause with an 'and'
with NewOntology(context, "test_i_can_use_multiple_where_clause"):
one_2, one_1, two_2, two_1, foo = get_concepts(context,
get_concept("one", body="2"),
get_concept("one", body="1"),
get_concept("two", body="2"),
get_concept("two", body="1"),
get_concept("foo",
body="a + b",
where="a == 1 and b == 2",
variables=(("a", "one"), ("b", "two"))),
use_sheerka=True)
res = service.evaluate_concept(context, foo)
assert context.sheerka.isinstance(res, foo)
assert res.body == 3
assert ConceptDefaultProps.WHERE in res.get_runtime_info().info
def test_where_clause_can_use_other_concept(self, context, service):
with NewOntology(context, "test_where_clause_can_use_other_concept"):
foo1, foo2, true, false = get_concepts(context,
get_concept("foo1", where="true", body="'evaluated !'"),
get_concept("foo2", where="false", body="'not evaluated !'"),
get_concept("true", body="True"),
get_concept("false", body="False"),
use_sheerka=True)
res = service.evaluate_concept(context, foo1)
assert context.sheerka.isinstance(res, foo1)
assert res.body == "evaluated !"
res = service.evaluate_concept(context, foo2)
assert context.sheerka.isinstance(res, BuiltinConcepts.EVALUATION_ERROR)
assert res.concept.body is NotInit
def test_i_can_detect_when_where_clause_failed_on_an_attribute(self, context, service):
with NewOntology(context, "test_i_can_detect_when_where_clause_failed_on_an_attribute"):
foo, one = get_concepts(context,
get_concept("foo", body="a + 1", where="a == 3", variables=(("a", "one"),)),
get_concept("one", body="1"),
use_sheerka=True)
res = service.evaluate_concept(context, foo)
assert context.sheerka.isinstance(res, BuiltinConcepts.EVALUATION_ERROR)
assert context.sheerka.isinstance(res.concept, foo)
assert isinstance(res.reason, dict) and "a" in res.reason
assert foo.get_runtime_info().is_evaluated
def test_i_can_use_where_constraint_on_multiple_levels(self, context, service):
with NewOntology(context, "test_i_can_use_where_constraint_on_multiple_levels"):
foo, bar, baz = get_concepts(context,
get_concept("foo", body="True"),
get_concept("bar", body="foo"),
get_concept("baz", where="bar"),
use_sheerka=True)
res = service.evaluate_concept(context, baz)
assert context.sheerka.isinstance(res, "baz")
assert ConceptDefaultProps.WHERE in res.get_runtime_info().info
def test_i_can_use_where_constraint_on_multiple_levels_and_fail(self, context, service):
with NewOntology(context, "test_i_can_use_where_constraint_on_multiple_levels_and_fail"):
foo, bar, baz = get_concepts(context,
get_concept("foo", body="False"),
get_concept("bar", body="foo"),
get_concept("baz", where="bar"),
use_sheerka=True)
res = service.evaluate_concept(context, baz)
assert context.sheerka.isinstance(res, BuiltinConcepts.EVALUATION_ERROR)
assert ConceptDefaultProps.WHERE in res.concept.get_runtime_info().error
def test_i_can_detect_infinite_loop(self, context, service):
with NewOntology(context, "test_i_can_detect_infinite_loop"):
foo, bar, baz = get_concepts(context,
get_concept("foo", body="bar"),
get_concept("bar", body="baz"),
get_concept("baz", body="foo"),
use_sheerka=True)
res = service.evaluate_concept(context, foo)
assert context.sheerka.isinstance(res, BuiltinConcepts.EVALUATION_ERROR)
assert context.sheerka.isinstance(res.concept, foo)
assert isinstance(res.reason, InfiniteRecursion)
assert res.reason.ids == [foo.id, bar.id, baz.id]
def test_i_can_detect_sub_infinite_loop(self, context, service):
with NewOntology(context, "test_i_can_detect_sub_infinite_loop"):
foo, bar, baz = get_concepts(context,
get_concept("foo", body="bar"),
get_concept("bar", body="baz"),
get_concept("baz", body="bar"),
use_sheerka=True)
res = service.evaluate_concept(context, foo)
assert context.sheerka.isinstance(res, BuiltinConcepts.EVALUATION_ERROR)
assert context.sheerka.isinstance(res.concept, bar)
assert isinstance(res.reason, InfiniteRecursion)
assert res.reason.ids == [bar.id, baz.id]
def test_i_can_detect_auto_infinite_loop(self, context, service):
with NewOntology(context, "test_i_can_detect_auto_infinite_loop"):
foo, = get_concepts(context,
get_concept("foo", body="foo"),
use_sheerka=True)
res = service.evaluate_concept(context, foo)
assert context.sheerka.isinstance(res, BuiltinConcepts.EVALUATION_ERROR)
assert context.sheerka.isinstance(res.concept, foo)
assert isinstance(res.reason, InfiniteRecursion)
assert res.reason.ids == [foo.id]
def test_i_can_select_the_valid_result_when_multiple_choice_invalid_concept(self, context, service):
with NewOntology(context, "test_i_can_select_the_valid_result_when_multiple_choice_invalid_concept"):
foo, two_ok, two_nok = get_concepts(context,
get_concept("foo", body="two"),
get_concept("two", body="1 +"), # has to come before the other 'two'
get_concept("two", body="1 + 1"),
use_sheerka=True)
res = service.evaluate_concept(context, foo)
assert context.sheerka.isinstance(res, foo)
assert context.sheerka.objvalue(foo) == 2
def test_i_can_select_the_valid_result_when_multiple_choice_evaluation_error(self, context, service):
with NewOntology(context, "test_i_can_select_the_valid_result_when_multiple_choice_evaluation_error"):
foo, two_ok, two_nok = get_concepts(context,
get_concept("foo", body="two"),
get_concept("two", body="1 / 0"), # has to come before the other 'two'
get_concept("two", body="1 + 1"),
use_sheerka=True)
res = service.evaluate_concept(context, foo)
assert context.sheerka.isinstance(res, foo)
assert context.sheerka.objvalue(foo) == 2
def test_i_can_eval_when_a_return_value_is_defined(self, context, service):
with NewOntology(context, "test_i_can_eval_when_a_return_value_is_defined"):
foo, bar, baz = get_concepts(context,
get_concept("foo"),
get_concept("bar"),
get_concept("baz", body="foo", ret="bar"),
use_sheerka=True)
res = service.evaluate_concept(context, baz)
assert context.sheerka.isinstance(res, bar)
assert context.sheerka.isinstance(baz.body, foo)
def test_i_do_not_use_ret_in_case_of_error(self, context, service):
with NewOntology(context, "test_i_do_not_use_ret_in_case_of_error"):
foo, baz = get_concepts(context,
get_concept("foo"),
get_concept("baz", body="foo", ret="bar"), # Concept("bar") is not defined
use_sheerka=True)
res = service.evaluate_concept(context, baz)
assert context.sheerka.isinstance(res, BuiltinConcepts.EVALUATION_ERROR)
@pytest.mark.skip("Cannot remove concept")
def test_i_do_not_use_ret_in_case_of_error_when_concept_was_removed(self, context, service):
# Make sure that ret is not returned in case of UNKNOWN_CONCEPT error message
foo, bar, baz = get_concepts(context,
get_concept("foo"),
get_concept("bar"),
get_concept("baz", body="foo", ret="bar"), # Concept("bar") is not defined
use_sheerka=True)
service.evaluate_concept(context, baz) # creates the compiled for Concept("baz")
context.sheerka.remove_concept(bar) # Concept("bar") no longer exists, but compiled for "baz" remains the same
res = service.evaluate_concept(context, baz)
assert context.sheerka.isinstance(res, BuiltinConcepts.EVALUATION_ERROR)
assert "#ret#" in res.reason
assert res.reason["#ret#"].value == context.sheerka.newn(BuiltinConcepts.UNKNOWN_CONCEPT, requested="bar")
def test_i_cannot_evaluate_when_error(self, context, service):
with NewOntology(context, "test_i_cannot_evaluate_when_error"):
foo, = get_concepts(context,
get_concept("foo", body="I am a concept"), # "one" does not exist
use_sheerka=True)
res = service.evaluate_concept(context, foo)
assert context.sheerka.isinstance(res, BuiltinConcepts.INVALID_CONCEPT)