392 lines
16 KiB
Python
392 lines
16 KiB
Python
import ast
|
|
|
|
import pytest
|
|
|
|
from core.builtin_concepts import ReturnValueConcept, ParserResultConcept, BuiltinConcepts
|
|
from core.builtin_helpers import CreateObjectIdentifiers
|
|
from core.concept import Concept, CB
|
|
from core.sheerka.services.SheerkaConceptManager import SheerkaConceptManager
|
|
from core.sheerka.services.SheerkaExecute import ParserInput
|
|
from core.tokenizer import Tokenizer
|
|
from evaluators.PythonEvaluator import PythonEvaluator, PythonEvalError, NamesWithAttributesVisitor
|
|
from parsers.BaseNodeParser import SourceCodeNode, SourceCodeWithConceptNode
|
|
from parsers.FunctionParser import FunctionParser
|
|
from parsers.PythonParser import PythonNode, PythonParser
|
|
from parsers.PythonWithConceptsParser import PythonWithConceptsParser
|
|
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
|
|
|
|
|
|
def get_obj_name(obj):
|
|
return obj.name
|
|
|
|
|
|
def return_return_value(status):
|
|
return ReturnValueConcept("who", status, f"the value is {status}")
|
|
|
|
|
|
def get_source_code_node(source_code, concepts=None):
|
|
if concepts:
|
|
for concept_name, concept in sorted(concepts.items(), key=lambda kv: len(kv[0]), reverse=True):
|
|
identifier = "__C__" + CreateObjectIdentifiers.sanitize(concept.name)
|
|
if concept.id:
|
|
identifier += "__" + concept.id
|
|
identifier += "__C__"
|
|
source_code = source_code.replace(concept_name, identifier)
|
|
concepts[identifier] = concept
|
|
|
|
if source_code:
|
|
python_node = PythonNode(source_code, ast.parse(source_code, f"<source>", 'eval'))
|
|
else:
|
|
python_node = PythonNode("", None)
|
|
|
|
if concepts is None:
|
|
tokens = list(Tokenizer(source_code, yield_eof=False))
|
|
return SourceCodeNode(0, len(tokens), tokens, python_node=python_node)
|
|
else:
|
|
python_node.objects = concepts
|
|
scwcn = SourceCodeWithConceptNode(None, None)
|
|
scwcn.python_node = python_node
|
|
return scwcn
|
|
|
|
|
|
def get_ret_val_from_source_code(context, source_code, concepts):
|
|
parsed = get_source_code_node(source_code, concepts)
|
|
return context.sheerka.ret("parsers.??", True, ParserResultConcept(value=parsed))
|
|
|
|
|
|
class TestPythonEvaluator(TestUsingMemoryBasedSheerka):
|
|
|
|
@pytest.mark.parametrize("ret_val, expected", [
|
|
(ReturnValueConcept("some_name", True, ParserResultConcept(value=PythonNode("", None))), True),
|
|
(ReturnValueConcept("some_name", True, ParserResultConcept(value=get_source_code_node(""))), True),
|
|
(ReturnValueConcept("some_name", True, ParserResultConcept(value=get_source_code_node("", {}))), True),
|
|
(ReturnValueConcept("some_name", True, ParserResultConcept(value="other thing")), False),
|
|
(ReturnValueConcept("some_name", False, "not relevant"), False),
|
|
(ReturnValueConcept("some_name", True, Concept()), False)
|
|
])
|
|
def test_i_can_match(self, ret_val, expected):
|
|
context = self.get_context()
|
|
assert PythonEvaluator().matches(context, ret_val) == expected
|
|
|
|
@pytest.mark.parametrize("text, expected", [
|
|
("1 + 1", 2),
|
|
("test()", "I have access to Sheerka !"),
|
|
("sheerka.test()", "I have access to Sheerka !"),
|
|
("a=10\na", 10),
|
|
("Concept('foo')", Concept('foo')),
|
|
("BuiltinConcepts.NOP", BuiltinConcepts.NOP),
|
|
("in_context(BuiltinConcepts.EVAL_QUESTION_REQUESTED)", False),
|
|
])
|
|
def test_i_can_eval(self, text, expected):
|
|
context = self.get_context()
|
|
parsed = PythonParser().parse(context, ParserInput(text))
|
|
|
|
evaluated = PythonEvaluator().eval(context, parsed)
|
|
|
|
assert evaluated.status
|
|
assert evaluated.value == expected
|
|
|
|
def test_i_can_eval_isinstance(self):
|
|
sheerka, context, foo = self.init_concepts("foo")
|
|
parsed = PythonParser().parse(context, ParserInput("isinstance('foo', str)"))
|
|
evaluated = PythonEvaluator().eval(context, parsed)
|
|
assert evaluated.status
|
|
assert evaluated.value
|
|
|
|
parsed = PythonParser().parse(context, ParserInput("isinstance(foo, 'foo')"))
|
|
evaluated = PythonEvaluator().eval(context, parsed)
|
|
assert evaluated.status
|
|
assert evaluated.value
|
|
|
|
def test_i_can_eval_context_obj_properties(self):
|
|
sheerka, context, foo = self.init_concepts(Concept("foo").def_var("a", "hello world!").auto_init())
|
|
context = self.get_context()
|
|
parsed = PythonParser().parse(context, ParserInput("a"))
|
|
|
|
context.obj = foo
|
|
evaluated = PythonEvaluator().eval(context, parsed)
|
|
|
|
assert evaluated.status
|
|
assert evaluated.value == "hello world!"
|
|
|
|
@pytest.mark.parametrize("source_code_node, expected", [
|
|
(get_source_code_node("1 + 1"), 2),
|
|
(get_source_code_node("one + one", {"one": Concept("one", body="1")}), 2)
|
|
])
|
|
def test_i_can_eval_source_code_node(self, source_code_node, expected):
|
|
context = self.get_context()
|
|
return_value = context.sheerka.ret("parsers.??", True, ParserResultConcept(value=source_code_node))
|
|
|
|
evaluated = PythonEvaluator().eval(context, return_value)
|
|
|
|
assert evaluated.status
|
|
assert evaluated.value == expected
|
|
|
|
def test_i_can_eval_a_method_that_requires_the_context(self):
|
|
context = self.get_context()
|
|
parsed = PythonParser().parse(context, ParserInput("test_using_context('value for param')"))
|
|
|
|
evaluated = PythonEvaluator().eval(context, parsed)
|
|
|
|
assert evaluated.status
|
|
assert evaluated.value == "I have access to Sheerka ! param='value for param', event='xxx'."
|
|
|
|
def test_i_can_eval_using_context_when_self_is_not_sheerka(self):
|
|
sheerka, context = self.init_test().unpack()
|
|
parsed = PythonParser().parse(context, ParserInput("create_new_concept(Concept('foo'))"))
|
|
|
|
evaluated = PythonEvaluator().eval(context, parsed)
|
|
|
|
assert evaluated.status
|
|
assert sheerka.services[SheerkaConceptManager.NAME].has_key("foo")
|
|
|
|
def test_i_can_eval_ast_expression_that_references_concepts(self):
|
|
"""
|
|
I can test modules with variables
|
|
:return:
|
|
"""
|
|
context = self.get_context()
|
|
context.sheerka.test_only_add_in_cache(Concept("foo", body="1"))
|
|
|
|
parsed = PythonParser().parse(context, ParserInput("foo + 2"))
|
|
evaluated = PythonEvaluator().eval(context, parsed)
|
|
|
|
assert evaluated.status
|
|
assert evaluated.value == 3
|
|
|
|
def test_i_can_eval_ast_module_that_references_concepts(self):
|
|
"""
|
|
I can test modules with variables
|
|
:return:
|
|
"""
|
|
context = self.get_context()
|
|
context.sheerka.test_only_add_in_cache(Concept("foo"))
|
|
|
|
parsed = PythonParser().parse(context, ParserInput("def a(b):\n return b\na(c:foo:)"))
|
|
evaluated = PythonEvaluator().eval(context, parsed)
|
|
|
|
assert evaluated.status
|
|
assert evaluated.value == Concept("foo").init_key()
|
|
|
|
def test_i_can_eval_ast_module_that_references_concepts_with_body(self):
|
|
"""
|
|
I can test modules with variables
|
|
:return:
|
|
"""
|
|
sheerka, context, foo = self.init_concepts(Concept("foo", body="2"))
|
|
|
|
parsed = PythonParser().parse(context, ParserInput("def a(b):\n return b\na(foo)"))
|
|
evaluated = PythonEvaluator().eval(context, parsed)
|
|
|
|
assert evaluated.status
|
|
assert evaluated.value == CB("foo", 2)
|
|
|
|
def test_i_can_eval_concept_token(self):
|
|
context = self.get_context()
|
|
context.sheerka.test_only_add_in_cache(Concept("foo", body="2"))
|
|
context.add_to_short_term_memory("get_obj_name", get_obj_name)
|
|
|
|
parsed = PythonParser().parse(context, ParserInput("get_obj_name(c:foo:)"))
|
|
python_evaluator = PythonEvaluator()
|
|
evaluated = python_evaluator.eval(context, parsed)
|
|
|
|
assert evaluated.status
|
|
assert evaluated.value == "foo"
|
|
|
|
def test_i_can_eval_when_expect_success(self):
|
|
context = self.get_context()
|
|
context.sheerka.test_only_add_in_cache(Concept("foo", body="2"))
|
|
|
|
parsed = PythonParser().parse(context, ParserInput("foo==2"))
|
|
python_evaluator = PythonEvaluator()
|
|
|
|
evaluated = python_evaluator.eval(context, parsed)
|
|
assert evaluated.status
|
|
assert not evaluated.value # the first test is between Concept(foo) and int(2)
|
|
|
|
context.protected_hints.add(BuiltinConcepts.EVAL_UNTIL_SUCCESS_REQUESTED)
|
|
evaluated = python_evaluator.eval(context, parsed)
|
|
assert evaluated.status
|
|
assert evaluated.value # we test until we compare foo.body and 2
|
|
|
|
parsed = PythonParser().parse(context, ParserInput("foo==3"))
|
|
evaluated = python_evaluator.eval(context, parsed)
|
|
assert evaluated.status
|
|
assert not evaluated.value # neither foo or foo.body ==3
|
|
|
|
def test_i_can_call_function_with_complex_concepts(self):
|
|
sheerka, context, plus, mult = self.init_concepts(
|
|
self.from_def_concept("plus", "a plus b", ["a", "b"]),
|
|
self.from_def_concept("mult", "a mult b", ["a", "b"]),
|
|
)
|
|
|
|
parsed = PythonParser().parse(context,
|
|
ParserInput("set_is_greater_than(BuiltinConcepts.PRECEDENCE, mult, plus)"))
|
|
python_evaluator = PythonEvaluator()
|
|
|
|
evaluated = python_evaluator.eval(context, parsed)
|
|
|
|
assert evaluated.status
|
|
assert sheerka.get_weights(BuiltinConcepts.PRECEDENCE) == {'c:__var__0 plus __var__1|1001:': 1,
|
|
'c:__var__0 mult __var__1|1002:': 2}
|
|
|
|
def test_i_can_define_variables(self):
|
|
sheerka, context = self.init_test().unpack()
|
|
parsed = PythonParser().parse(context, ParserInput("a=10"))
|
|
python_evaluator = PythonEvaluator()
|
|
|
|
python_evaluator.eval(context, parsed)
|
|
|
|
parsed = PythonParser().parse(context, ParserInput("a"))
|
|
evaluated = python_evaluator.eval(context, parsed)
|
|
|
|
assert evaluated.status
|
|
assert evaluated.body == 10
|
|
|
|
def test_i_can_get_all_possibles_globals(self):
|
|
sheerka, context, foo = self.init_concepts(Concept("foo", body="foo").auto_init())
|
|
python_evaluator = PythonEvaluator()
|
|
my_globals = {
|
|
"a": "a string",
|
|
"b": self.test_i_can_get_all_possibles_globals,
|
|
"foo": foo
|
|
}
|
|
|
|
all_globals = python_evaluator.get_all_possible_globals(context, my_globals)
|
|
assert len(all_globals) == 2
|
|
assert all_globals[0]["foo"] == CB(foo, "foo")
|
|
assert all_globals[1]["foo"] == 'foo' # body is evaluated
|
|
|
|
def test_i_can_detect_one_error(self):
|
|
sheerka, context, foo = self.init_concepts("foo")
|
|
|
|
parsed = PythonParser().parse(context, ParserInput("foo + 1"))
|
|
evaluated = PythonEvaluator().eval(context, parsed)
|
|
|
|
assert not evaluated.status
|
|
assert context.sheerka.isinstance(evaluated.value, BuiltinConcepts.ERROR)
|
|
|
|
error = evaluated.body.body
|
|
assert isinstance(error, PythonEvalError)
|
|
assert isinstance(error.error, TypeError)
|
|
assert error.error.args[0] == "unsupported operand type(s) for +: 'Concept' and 'int'"
|
|
assert error.concepts == {'foo': foo}
|
|
|
|
def test_i_can_detect_multiple_errors(self):
|
|
sheerka, context, foo = self.init_concepts(Concept("foo", body="'string'"))
|
|
|
|
parsed = PythonParser().parse(context, ParserInput("foo + 1"))
|
|
evaluated = PythonEvaluator().eval(context, parsed)
|
|
|
|
assert not evaluated.status
|
|
assert context.sheerka.isinstance(evaluated.value, BuiltinConcepts.ERROR)
|
|
|
|
error0 = evaluated.body.body[0]
|
|
assert isinstance(error0, PythonEvalError)
|
|
assert isinstance(error0.error, TypeError)
|
|
assert error0.error.args[0] == "unsupported operand type(s) for +: 'Concept' and 'int'"
|
|
assert error0.concepts == {'foo': CB(foo, 'string')}
|
|
|
|
error1 = evaluated.body.body[1]
|
|
assert isinstance(error1, PythonEvalError)
|
|
assert isinstance(error1.error, TypeError)
|
|
assert error1.error.args[0] == 'can only concatenate str (not "int") to str'
|
|
assert error1.concepts == {'foo': 'string'}
|
|
|
|
def test_i_can_use_sheerka_locals(self):
|
|
sheerka, context = self.init_test().unpack()
|
|
|
|
def func(i):
|
|
return i + 1
|
|
|
|
sheerka.locals["func"] = func
|
|
|
|
parsed = PythonParser().parse(context, ParserInput("func(10)"))
|
|
python_evaluator = PythonEvaluator()
|
|
evaluated = python_evaluator.eval(context, parsed)
|
|
|
|
assert evaluated.status
|
|
assert evaluated.value == 11
|
|
|
|
def test_i_can_eval_concept_with_ret(self):
|
|
sheerka, context, one, the = self.init_concepts("one", Concept("the a", ret="a").def_var("a"))
|
|
ret_val = get_ret_val_from_source_code(context, "test_using_context(the one)", {
|
|
"the one": self.get_concept_instance(sheerka, the, a="one")
|
|
})
|
|
|
|
evaluated = PythonEvaluator().eval(context, ret_val)
|
|
assert evaluated.status
|
|
assert evaluated.value == "I have access to Sheerka ! param=(1001)one, event='xxx'."
|
|
|
|
def test_i_can_eval_rules_from_python_parser(self):
|
|
sheerka, context = self.init_test().unpack()
|
|
parsed_ret_val = PythonParser().parse(context, ParserInput("r:|1:.id"))
|
|
|
|
assert parsed_ret_val.status
|
|
|
|
evaluated = PythonEvaluator().eval(context, parsed_ret_val)
|
|
assert evaluated.status
|
|
assert evaluated.value == "1"
|
|
|
|
def test_i_can_eval_rules_from_function_parser(self):
|
|
context = self.get_context()
|
|
context.add_to_short_term_memory("get_obj_name", get_obj_name)
|
|
|
|
parsed = FunctionParser().parse(context, ParserInput("get_obj_name(r:|1:)"))
|
|
python_evaluator = PythonEvaluator()
|
|
evaluated = python_evaluator.eval(context, parsed)
|
|
|
|
assert evaluated.status
|
|
assert evaluated.value == "Print return values"
|
|
|
|
@pytest.mark.parametrize("method, expected_status", [
|
|
("return_return_value(True)", True),
|
|
("return_return_value(False)", False),
|
|
])
|
|
def test_i_can_eval_a_function_that_returns_a_return_value(self, method, expected_status):
|
|
context = self.get_context()
|
|
context.add_to_short_term_memory("return_return_value", return_return_value)
|
|
|
|
parsed = FunctionParser().parse(context, ParserInput(method))
|
|
python_evaluator = PythonEvaluator()
|
|
evaluated = python_evaluator.eval(context, parsed)
|
|
ret_val = return_return_value(expected_status)
|
|
|
|
assert evaluated.status == expected_status
|
|
assert evaluated.value == ret_val.body
|
|
assert ret_val in evaluated.parents
|
|
|
|
@pytest.mark.parametrize("text, expected", [
|
|
("foo.bar.baz", [["foo", "bar", "baz"]]),
|
|
("foo.bar.baz; one.two.three", [["foo", "bar", "baz"]]),
|
|
("foo.bar.baz; one.two.three; foo.bar", [["foo", "bar", "baz"], ["foo", "bar"]]),
|
|
("one.two.three", []),
|
|
("foo", [["foo"]]),
|
|
("foo.bar[1].baz", [["foo", "bar", "baz"]]),
|
|
("foo[1].bar.baz", [["foo", "bar", "baz"]]),
|
|
])
|
|
def test_names_with_attributes_visitor(self, text, expected):
|
|
ast_ = ast.parse(text, "<src>", mode="exec")
|
|
visitor = NamesWithAttributesVisitor()
|
|
|
|
sequence = visitor.get_sequences(ast_, "foo")
|
|
assert sequence == expected
|
|
|
|
@pytest.mark.parametrize("parser, value", [
|
|
(PythonParser(), "3"),
|
|
(FunctionParser(), "3"),
|
|
(PythonParser(), 3),
|
|
(FunctionParser(), 3),
|
|
])
|
|
def test_i_can_eval_unresolved_rules(self, parser, value):
|
|
context = self.get_context()
|
|
context.add_to_short_term_memory("get_obj_name", get_obj_name)
|
|
context.add_to_short_term_memory("x", value)
|
|
|
|
parsed = parser.parse(context, ParserInput("get_obj_name(r:|x:)"))
|
|
python_evaluator = PythonEvaluator()
|
|
evaluated = python_evaluator.eval(context, parsed)
|
|
|
|
assert evaluated.status
|
|
assert evaluated.value == context.sheerka.get_rule_by_id(str(value)).name
|