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)