528 lines
17 KiB
Python
528 lines
17 KiB
Python
import os
|
|
import shutil
|
|
from os import path
|
|
|
|
import pytest
|
|
|
|
from core.builtin_concepts import BuiltinConcepts
|
|
from core.concept import Concept, PROPERTIES_TO_SERIALIZE, Property
|
|
from core.sheerka import Sheerka, ExecutionContext
|
|
from evaluators.MutipleSameSuccessEvaluator import MultipleSameSuccessEvaluator
|
|
from parsers.ConceptLexerParser import Sequence, StrMatch, OrderedChoice, Optional, ConceptMatch
|
|
from sdp.sheerkaDataProvider import SheerkaDataProvider, Event
|
|
|
|
tests_root = path.abspath("../build/tests")
|
|
root_folder = "init_folder"
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def init_test():
|
|
if path.exists(tests_root):
|
|
shutil.rmtree(tests_root)
|
|
|
|
if not path.exists(tests_root):
|
|
os.makedirs(tests_root)
|
|
current_pwd = os.getcwd()
|
|
os.chdir(tests_root)
|
|
|
|
yield None
|
|
|
|
os.chdir(current_pwd)
|
|
|
|
|
|
def get_sheerka(use_dict=True, skip_builtins_in_db=True):
|
|
root = "mem://" if use_dict else root_folder
|
|
sheerka = Sheerka(skip_builtins_in_db=skip_builtins_in_db)
|
|
sheerka.initialize(root)
|
|
|
|
return sheerka
|
|
|
|
|
|
def get_context(sheerka):
|
|
return ExecutionContext("test", Event(), sheerka)
|
|
|
|
|
|
def get_default_concept():
|
|
concept = Concept(
|
|
name="a + b",
|
|
where="isinstance(a, int) and isinstance(b, int)",
|
|
pre="isinstance(a, int) and isinstance(b, int)",
|
|
post="isinstance(res, int)",
|
|
body="def func(x,y):\n return x+y\nfunc(a,b)",
|
|
desc="specific description")
|
|
concept.set_prop("a", "value1")
|
|
concept.set_prop("b", "value2")
|
|
|
|
return concept
|
|
|
|
|
|
@pytest.mark.parametrize("text, expected", [
|
|
("1 + 1", 2),
|
|
("sheerka.test()", 'I have access to Sheerka !')
|
|
])
|
|
def test_i_can_eval_python_expressions_with_no_variable(text, expected):
|
|
sheerka = get_sheerka()
|
|
|
|
res = sheerka.evaluate_user_input(text)
|
|
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert res[0].value == expected
|
|
|
|
|
|
def test_i_can_eval_concept_with_python_body():
|
|
sheerka = get_sheerka()
|
|
concept = Concept(name="one", body="1")
|
|
sheerka.add_in_cache(concept)
|
|
|
|
text = "one"
|
|
res = sheerka.evaluate_user_input(text)
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert res[0].value == Concept(name="one", body=1).init_key() # by default, the concept is returned
|
|
|
|
|
|
def test_i_can_eval_concept_with_concept_body():
|
|
sheerka = get_sheerka()
|
|
concept_one = Concept(name="one")
|
|
concept_un = Concept(name="un", body="one")
|
|
sheerka.add_in_cache(concept_one)
|
|
sheerka.add_in_cache(concept_un)
|
|
|
|
res = sheerka.evaluate_user_input("un")
|
|
return_value = res[0].value
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert return_value == Concept(name="un", body=Concept(name="one").init_key()).init_key()
|
|
|
|
|
|
def test_i_can_eval_concept_with_no_body():
|
|
sheerka = get_sheerka()
|
|
concept = Concept(name="one")
|
|
sheerka.add_in_cache(concept)
|
|
|
|
text = "one"
|
|
res = sheerka.evaluate_user_input(text)
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert res[0].value == concept
|
|
assert id(res[0].value) != id(concept)
|
|
|
|
|
|
def test_is_unique_property_is_used_when_evaluating():
|
|
sheerka = get_sheerka()
|
|
concept = Concept(name="one", is_unique=True)
|
|
sheerka.add_in_cache(concept)
|
|
|
|
text = "one"
|
|
res = sheerka.evaluate_user_input(text)
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert res[0].value == concept
|
|
assert id(res[0].value) == id(concept)
|
|
|
|
|
|
def test_i_can_eval_def_concept_request():
|
|
text = """
|
|
def concept a + b
|
|
where isinstance(a, int) and isinstance(b, int)
|
|
pre isinstance(a, int) and isinstance(b, int)
|
|
post isinstance(res, int)
|
|
as:
|
|
def func(x,y):
|
|
return x+y
|
|
func(a,b)
|
|
"""
|
|
|
|
expected = get_default_concept()
|
|
expected.metadata.id = "1001"
|
|
expected.metadata.desc = None
|
|
expected.init_key()
|
|
|
|
sheerka = get_sheerka()
|
|
res = sheerka.evaluate_user_input(text)
|
|
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert sheerka.isinstance(res[0].value, BuiltinConcepts.NEW_CONCEPT)
|
|
|
|
concept_saved = res[0].value.body
|
|
|
|
for prop in PROPERTIES_TO_SERIALIZE:
|
|
assert getattr(concept_saved.metadata, prop) == getattr(expected.metadata, prop)
|
|
|
|
assert concept_saved.key in sheerka.concepts_cache
|
|
assert sheerka.sdp.io.exists(
|
|
sheerka.sdp.io.get_obj_path(SheerkaDataProvider.ObjectsFolder, concept_saved.get_digest()))
|
|
|
|
|
|
def test_i_can_eval_def_concept_part_when_one_part_is_a_ref_of_another_concept():
|
|
"""
|
|
In this test, we test that the properties of 'concept a xx b' (which are 'a' and 'b')
|
|
are correctly detected, thanks to the source code 'a plus b' in its body
|
|
:return:
|
|
"""
|
|
sheerka = get_sheerka()
|
|
|
|
# concept 'a plus b' is known
|
|
concept_a_plus_b = Concept(name="a plus b").set_prop("a").set_prop("b")
|
|
sheerka.add_in_cache(concept_a_plus_b)
|
|
|
|
res = sheerka.evaluate_user_input("def concept a xx b as a plus b")
|
|
expected = Concept(name="a xx b", body="a plus b").set_prop("a").set_prop("b").init_key()
|
|
expected.metadata.id = "1001"
|
|
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert sheerka.isinstance(res[0].value, BuiltinConcepts.NEW_CONCEPT)
|
|
|
|
concept_saved = res[0].value.body
|
|
|
|
for prop in PROPERTIES_TO_SERIALIZE:
|
|
assert getattr(concept_saved.metadata, prop) == getattr(expected.metadata, prop)
|
|
|
|
assert concept_saved.key in sheerka.concepts_cache
|
|
assert sheerka.sdp.io.exists(
|
|
sheerka.sdp.io.get_obj_path(SheerkaDataProvider.ObjectsFolder, concept_saved.get_digest()))
|
|
|
|
|
|
def test_i_cannot_eval_the_same_def_concept_twice():
|
|
text = """
|
|
def concept a + b
|
|
where isinstance(a, int) and isinstance(b, int)
|
|
pre isinstance(a, int) and isinstance(b, int)
|
|
post isinstance(res, int)
|
|
as:
|
|
def func(x,y):
|
|
return x+y
|
|
func(a,b)
|
|
"""
|
|
|
|
sheerka = get_sheerka()
|
|
sheerka.evaluate_user_input(text)
|
|
res = sheerka.evaluate_user_input(text)
|
|
|
|
assert len(res) == 1
|
|
assert not res[0].status
|
|
assert sheerka.isinstance(res[0].value, BuiltinConcepts.CONCEPT_ALREADY_DEFINED)
|
|
|
|
|
|
@pytest.mark.parametrize("text", [
|
|
"",
|
|
" ",
|
|
"\n",
|
|
])
|
|
def test_i_can_eval_a_empty_input(text):
|
|
sheerka = get_sheerka()
|
|
|
|
res = sheerka.evaluate_user_input(text)
|
|
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert sheerka.isinstance(res[0].value, BuiltinConcepts.NOP)
|
|
|
|
|
|
def test_i_can_eval_concept_with_variable():
|
|
sheerka = get_sheerka()
|
|
concept_hello = Concept(name="hello a").set_prop("a")
|
|
concept_foo = Concept(name="foo")
|
|
sheerka.add_in_cache(concept_hello)
|
|
sheerka.add_in_cache(concept_foo)
|
|
|
|
res = sheerka.evaluate_user_input("hello foo")
|
|
return_value = res[0].value
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert sheerka.isinstance(return_value, concept_hello)
|
|
assert return_value.props["a"].value == concept_foo
|
|
|
|
|
|
def test_i_can_eval_concept_with_variable_and_python_as_body():
|
|
sheerka = get_sheerka()
|
|
hello_a = sheerka.add_in_cache(Concept(name="hello a", body="'hello ' + a").set_prop("a"))
|
|
sheerka.add_in_cache(Concept(name="foo", body="'foo'"))
|
|
|
|
res = sheerka.evaluate_user_input("hello foo")
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert sheerka.isinstance(res[0].value, hello_a)
|
|
assert res[0].value.body == "hello foo"
|
|
assert res[0].value.metadata.is_evaluated
|
|
assert res[0].value.props["a"].value == Concept(name="foo", body="foo").init_key()
|
|
assert res[0].value.props["a"].value.metadata.is_evaluated
|
|
|
|
|
|
def test_i_can_eval_duplicate_concepts_with_same_value():
|
|
sheerka = get_sheerka()
|
|
|
|
sheerka.add_in_cache(Concept(name="hello a", body="'hello ' + a").set_prop("a"))
|
|
sheerka.add_in_cache(Concept(name="hello foo", body="'hello foo'"))
|
|
sheerka.add_in_cache(Concept(name="foo", body="'foo'"))
|
|
|
|
res = sheerka.evaluate_user_input("hello foo")
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert res[0].value.body == "hello foo"
|
|
assert res[0].who == sheerka.get_evaluator_name(MultipleSameSuccessEvaluator.NAME)
|
|
|
|
|
|
def test_i_cannot_manage_duplicate_concepts_when_the_values_are_different():
|
|
sheerka = get_sheerka()
|
|
|
|
sheerka.add_in_cache(Concept(name="hello a", body="'hello ' + a").set_prop("a"))
|
|
sheerka.add_in_cache(Concept(name="hello foo", body="'hello foo'"))
|
|
sheerka.add_in_cache(Concept(name="foo", body="'another value'"))
|
|
|
|
res = sheerka.evaluate_user_input("hello foo")
|
|
assert len(res) == 1
|
|
assert not res[0].status
|
|
assert sheerka.isinstance(res[0].value, BuiltinConcepts.TOO_MANY_SUCCESS)
|
|
|
|
concepts = res[0].value.body
|
|
assert len(concepts) == 2
|
|
sorted_values = sorted(concepts, key=lambda x: x.value.body)
|
|
assert sorted_values[0].value.body == "hello another value"
|
|
assert sorted_values[1].value.body == "hello foo"
|
|
|
|
|
|
def test_i_can_manage_concepts_with_the_same_key_when_values_are_the_same():
|
|
sheerka = get_sheerka()
|
|
context = get_context(sheerka)
|
|
|
|
sheerka.create_new_concept(context, Concept(name="hello a", body="'hello ' + a").set_prop("a"))
|
|
sheerka.create_new_concept(context, Concept(name="hello b", body="'hello ' + b").set_prop("b"))
|
|
|
|
res = sheerka.evaluate_user_input("hello 'foo'")
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert res[0].value.body == "hello foo" # I don't know yet the one to choose
|
|
assert res[0].who == sheerka.get_evaluator_name(MultipleSameSuccessEvaluator.NAME)
|
|
|
|
|
|
def test_i_can_create_concepts_with_python_code_as_body():
|
|
sheerka = get_sheerka()
|
|
context = get_context(sheerka)
|
|
|
|
sheerka.create_new_concept(context, Concept(name="concepts", body="sheerka.concepts()"))
|
|
res = sheerka.evaluate_user_input("concepts")
|
|
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert isinstance(res[0].value.body, list)
|
|
|
|
|
|
def test_i_can_create_concept_with_bnf_definition():
|
|
sheerka = get_sheerka(False, False)
|
|
a = Concept("a")
|
|
sheerka.add_in_cache(a)
|
|
sheerka.concepts_definition_cache = {a: OrderedChoice("one", "two")}
|
|
|
|
res = sheerka.evaluate_user_input("def concept plus from bnf a ('plus' plus)?")
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert sheerka.isinstance(res[0].value, BuiltinConcepts.NEW_CONCEPT)
|
|
|
|
saved_concept = sheerka.sdp.get_safe(sheerka.CONCEPTS_ENTRY, "plus")
|
|
assert saved_concept.key == "plus"
|
|
assert saved_concept.metadata.definition == "a ('plus' plus)?"
|
|
assert "a" in saved_concept.props
|
|
assert "plus" in saved_concept.props
|
|
|
|
saved_definitions = sheerka.sdp.get_safe(sheerka.CONCEPTS_DEFINITIONS_ENTRY)
|
|
expected_bnf = Sequence(
|
|
ConceptMatch("a", rule_name="a"),
|
|
Optional(Sequence(StrMatch("plus"), ConceptMatch("plus", rule_name="plus"))))
|
|
assert saved_definitions[saved_concept] == expected_bnf
|
|
|
|
new_concept = res[0].value.body
|
|
assert new_concept.metadata.name == "plus"
|
|
assert new_concept.metadata.definition == "a ('plus' plus)?"
|
|
assert new_concept.bnf == expected_bnf
|
|
assert "a" in new_concept.props
|
|
assert "plus" in new_concept.props
|
|
|
|
|
|
def test_i_can_eval_bnf_definitions():
|
|
sheerka = get_sheerka()
|
|
concept_a = sheerka.evaluate_user_input("def concept a from bnf 'one' | 'two'")[0].body.body
|
|
|
|
res = sheerka.evaluate_user_input("one")
|
|
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert sheerka.isinstance(res[0].value, concept_a)
|
|
|
|
|
|
def test_i_can_eval_bnf_definitions_with_variables():
|
|
sheerka = get_sheerka()
|
|
concept_a = sheerka.evaluate_user_input("def concept a from bnf 'one' | 'two'")[0].body.body
|
|
concept_b = sheerka.evaluate_user_input("def concept b from bnf a 'three'")[0].body.body
|
|
|
|
res = sheerka.evaluate_user_input("one three")
|
|
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
return_value = res[0].value
|
|
|
|
assert sheerka.isinstance(return_value, concept_b)
|
|
assert return_value.body == "one three"
|
|
assert return_value.metadata.is_evaluated
|
|
|
|
assert return_value.props["a"] == Property("a", sheerka.new(concept_a.key, body="one").init_key())
|
|
assert return_value.props["a"].value.metadata.is_evaluated
|
|
|
|
|
|
def test_i_can_eval_bnf_definitions_from_separate_instances():
|
|
"""
|
|
Same test then before,
|
|
but make sure that the BNF are correctly persisted and loaded
|
|
"""
|
|
sheerka = get_sheerka(False)
|
|
concept_a = sheerka.evaluate_user_input("def concept a from bnf 'one' 'two'")[0].body.body
|
|
|
|
res = get_sheerka(False).evaluate_user_input("one two")
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert sheerka.isinstance(res[0].value, concept_a)
|
|
|
|
# add another bnf definition
|
|
concept_b = sheerka.evaluate_user_input("def concept b from bnf a 'three'")[0].body.body
|
|
|
|
res = get_sheerka(False).evaluate_user_input("one two") # previous one still works
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert sheerka.isinstance(res[0].value, concept_a)
|
|
|
|
res = get_sheerka(False).evaluate_user_input("one two three") # new one works
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert sheerka.isinstance(res[0].value, concept_b)
|
|
assert res[0].value.body == "one two three"
|
|
assert res[0].value.props["a"] == Property("a", sheerka.new(concept_a.key, body="one two").init_key())
|
|
|
|
|
|
def test_i_can_eval_a_mix_with_bnf_and_python():
|
|
sheerka = get_sheerka()
|
|
|
|
sheerka.evaluate_user_input("def concept one as 1")
|
|
sheerka.evaluate_user_input("def concept twenties from bnf 'twenty' one as 20 + one")
|
|
|
|
res = sheerka.evaluate_user_input("twenty one + 1")
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert res[0].body == 22
|
|
|
|
|
|
def test_i_can_eval_a_mix_with_bnf_and_python_when_rule_name():
|
|
sheerka = get_sheerka()
|
|
|
|
sheerka.evaluate_user_input("def concept one as 1")
|
|
sheerka.evaluate_user_input("def concept two as 2")
|
|
sheerka.evaluate_user_input("def concept twenties from bnf 'twenty' (one|two)=unit as 20 + unit")
|
|
|
|
assert sheerka.evaluate_user_input("eval twenty one")[0].body == 21
|
|
|
|
res = sheerka.evaluate_user_input("twenty one + 1")
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert res[0].body == 22
|
|
|
|
|
|
def test_i_can_eval_a_more_complicated_mix_with_bnf_and_python():
|
|
sheerka = get_sheerka()
|
|
|
|
sheerka.evaluate_user_input("def concept one as 1")
|
|
sheerka.evaluate_user_input("def concept two as 2")
|
|
sheerka.evaluate_user_input("def concept twenties from bnf 'twenty' (one|two)=unit as 20 + unit")
|
|
|
|
assert sheerka.evaluate_user_input("eval twenty one")[0].body == 21
|
|
|
|
res = sheerka.evaluate_user_input("twenty one + twenty two")
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert res[0].body == 43
|
|
|
|
|
|
def test_i_can_say_that_a_concept_isa_another_concept():
|
|
sheerka = get_sheerka()
|
|
sheerka.evaluate_user_input("def concept foo")
|
|
sheerka.evaluate_user_input("def concept bar")
|
|
|
|
res = sheerka.evaluate_user_input("foo isa bar")
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert sheerka.isinstance(res[0].body, BuiltinConcepts.SUCCESS)
|
|
|
|
|
|
def test_eval_does_not_break_valid_result():
|
|
sheerka = get_sheerka()
|
|
sheerka.evaluate_user_input("def concept one as 1")
|
|
sheerka.evaluate_user_input("def concept two as 2")
|
|
|
|
res = sheerka.evaluate_user_input("one + two")
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert res[0].body == 3
|
|
|
|
res = sheerka.evaluate_user_input("eval one + two")
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert res[0].body == 3
|
|
|
|
|
|
@pytest.mark.parametrize("text", [
|
|
"'hello",
|
|
'"foo" + "string',
|
|
"c::",
|
|
"c:foo\nbar:",
|
|
"c:foo",
|
|
"def concept 'name",
|
|
"def concept name from bnf 'name"
|
|
])
|
|
def test_i_can_manage_tokenizer_error(text):
|
|
sheerka = get_sheerka()
|
|
sheerka.add_in_cache(Concept("foo"))
|
|
|
|
res = sheerka.evaluate_user_input(text)
|
|
|
|
assert len(res) > 1
|
|
for r in [r for r in res if r.who.startswith("parsers.")]:
|
|
assert not r.status
|
|
|
|
|
|
def test_i_can_recognize_concept_from_string():
|
|
sheerka = get_sheerka()
|
|
sheerka.add_in_cache(Concept("one", body="1"))
|
|
|
|
res = sheerka.evaluate_user_input("'one'")
|
|
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert res[0].body == "one"
|
|
|
|
res = sheerka.evaluate_user_input("eval 'one'")
|
|
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert res[0].body == "one"
|
|
|
|
|
|
@pytest.mark.parametrize("expression", [
|
|
"def concept twenties from bnf 'twenty' (one | two)=unit as 20 + unit",
|
|
"def concept twenties from bnf 'twenty' (one | two)=unit as twenty + unit",
|
|
"def concept twenties from bnf twenty (one | two)=unit as 20 + unit",
|
|
"def concept twenties from bnf twenty (one | two)=unit as twenty + unit",
|
|
])
|
|
def test_i_can_evaluate_bnf_concepts(expression):
|
|
sheerka = get_sheerka()
|
|
|
|
sheerka.evaluate_user_input("def concept one as 1")
|
|
sheerka.evaluate_user_input("def concept two as 2")
|
|
sheerka.evaluate_user_input("def concept twenty as 20")
|
|
sheerka.evaluate_user_input(expression)
|
|
res = sheerka.evaluate_user_input("eval twenty one")
|
|
|
|
assert len(res) == 1
|
|
assert res[0].status
|
|
assert res[0].body == 21
|