Added first version of DebugManager. Implemented draft of the rule engine

This commit is contained in:
2020-11-20 13:41:45 +01:00
parent cd066881b4
commit 315f8ea09b
156 changed files with 8388 additions and 2852 deletions
+355
View File
@@ -0,0 +1,355 @@
import ast
import pytest
from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept
from core.global_symbols import RULE_COMPARISON_CONTEXT
from core.rule import Rule
from core.sheerka.services.SheerkaRuleManager import SheerkaRuleManager, FormatRuleParser, \
FormatAstRawText, FormatAstVariable, FormatAstSequence, FormatAstFunction, \
FormatRuleSyntaxError, FormatAstList, UnexpectedEof, FormatAstColor, RulePredicate
from core.tokenizer import Token, TokenKind
from parsers.BaseNodeParser import SourceCodeWithConceptNode, SourceCodeNode
from parsers.PythonParser import PythonNode
from tests.TestUsingFileBasedSheerka import TestUsingFileBasedSheerka
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
seq = FormatAstSequence
raw = FormatAstRawText
var = FormatAstVariable
func = FormatAstFunction
lst = FormatAstList
PYTHON_EVALUATOR_NAME = "Python"
CONCEPT_EVALUATOR_NAME = "Concept"
class TestSheerkaRuleManager(TestUsingMemoryBasedSheerka):
@pytest.mark.parametrize("action_type, cache_entry", [
("print", SheerkaRuleManager.FORMAT_RULE_ENTRY),
("exec", SheerkaRuleManager.EXEC_RULE_ENTRY),
])
def test_i_can_create_a_new_rule(self, action_type, cache_entry):
sheerka, context = self.init_concepts(cache_only=False)
previous_rules_number = sheerka.cache_manager.caches[sheerka.CONCEPTS_KEYS_ENTRY].cache.copy()[
SheerkaRuleManager.RULE_IDS]
rule = Rule(action_type, "name", "True", "Hello world")
res = sheerka.create_new_rule(context, rule)
expected_id = str(previous_rules_number + 1)
assert res.status
assert sheerka.isinstance(res.body, BuiltinConcepts.NEW_RULE)
created_rule = res.body.body
assert created_rule.metadata.id == expected_id
assert created_rule.metadata.name == "name"
assert created_rule.metadata.predicate == "True"
assert created_rule.metadata.action_type == action_type
assert created_rule.metadata.action == "Hello world"
# saved in cache
assert len(sheerka.cache_manager.caches[cache_entry].cache) > 0
from_cache = sheerka.cache_manager.get(cache_entry, expected_id)
assert from_cache.metadata.id == expected_id
assert from_cache.metadata.name == "name"
assert from_cache.metadata.predicate == "True"
assert from_cache.metadata.action_type == action_type
assert from_cache.metadata.action == "Hello world"
sheerka.cache_manager.commit(context)
# saved in sdp
from_sdp = sheerka.sdp.get(cache_entry, expected_id)
assert from_sdp.metadata.id == expected_id
assert from_sdp.metadata.name == "name"
assert from_sdp.metadata.predicate == "True"
assert from_sdp.metadata.action_type == action_type
assert from_sdp.metadata.action == "Hello world"
def test_i_can_create_multiple_rules(self):
sheerka, context = self.init_concepts(cache_only=False)
previous_rules_number = len(sheerka.cache_manager.caches[SheerkaRuleManager.FORMAT_RULE_ENTRY].cache)
sheerka.create_new_rule(context, Rule("print", "name1", "True", "Hello world"))
sheerka.create_new_rule(context, Rule("print", "name2", "value() is __EXPLANATION", "list(value())"))
assert len(
sheerka.cache_manager.caches[SheerkaRuleManager.FORMAT_RULE_ENTRY].cache) == 2 + previous_rules_number
@pytest.mark.parametrize("text, expected", [
("", FormatAstRawText("")),
(" ", FormatAstRawText(" ")),
(" raw text ", FormatAstRawText(" raw text ")),
("{variable}", FormatAstVariable("variable")),
("{ variable }", FormatAstVariable("variable")),
(" xy {v} z", seq([raw(" xy "), var("v"), raw(" z")])),
(r"\{variable}", FormatAstRawText("{variable}")),
(r"\\{variable}", seq([raw("\\"), var("variable")])),
(r"\\\{variable}", FormatAstRawText(r"\{variable}")),
(r"{var1}{var2}", seq([var("var1"), var("var2")])),
("func()", FormatAstFunction("func", [], {})),
("func(a, 'string value', c)", FormatAstFunction("func", ["a", "'string value'", "c"], {})),
("func(a=10, b='string value')", FormatAstFunction("func", [], {"a": "10", "b": "'string value'"})),
("func('string value'='another string value')", func("func", [], {"'string value'": "'another string value'"})),
("red(' xy {v}')", FormatAstColor("red", seq([raw(" xy "), var("v")]))),
('blue(" xy {v}")', FormatAstColor("blue", seq([raw(" xy "), var("v")]))),
('green( xy )', FormatAstColor("green", var("xy"))),
('green()', FormatAstColor("green", raw(""))),
('green("")', FormatAstColor("green", raw(""))),
("list(var_name, 2, 'children')", FormatAstList("var_name", recurse_on="children", recursion_depth=2)),
("list(var_name, recursion_depth=2, recurse_on='children')", FormatAstList("var_name",
recurse_on="children",
recursion_depth=2)),
("list(var_name, recursion_depth=2, 'children')", FormatAstList("var_name", recursion_depth=2)),
("list(var_name, 'children', recursion_depth=2)", FormatAstList("var_name", recursion_depth=2)),
("list(var_name)", FormatAstList("var_name")),
("{obj.prop1.prop2[0].prop3['value']}", FormatAstVariable("obj.prop1.prop2[0].prop3['value']")),
("[{id}]", seq([raw("["), var("id"), raw("]")])),
("{variable:format}", FormatAstVariable("variable", "format")),
("{variable:3}", FormatAstVariable("variable", "3")),
(r"\not_a_function(a={var})", seq([raw("not_a_function(a="), var("var"), raw(")")])),
])
def test_i_can_parse_format_rule(self, text, expected):
assert FormatRuleParser(text).parse() == expected
@pytest.mark.parametrize("text, expected_error", [
("{", UnexpectedEof("while parsing variable", Token(TokenKind.LBRACE, "{", 0, 1, 1))),
("{var_name", UnexpectedEof("while parsing variable", Token(TokenKind.LBRACE, "{", 0, 1, 1))),
("{}", FormatRuleSyntaxError("variable name not found", None)),
("func(", UnexpectedEof("while parsing function", Token(TokenKind.IDENTIFIER, "func", 0, 1, 1))),
("func(a,b,c", UnexpectedEof("while parsing function", Token(TokenKind.IDENTIFIER, "func", 0, 1, 1))),
("func(a,,c", FormatRuleSyntaxError("no parameter found", Token(TokenKind.COMMA, ",", 7, 1, 8))),
("func(a,,c)", FormatRuleSyntaxError("no parameter found", Token(TokenKind.COMMA, ",", 7, 1, 8))),
("red(a,b)", FormatRuleSyntaxError("only one parameter supported", Token(TokenKind.IDENTIFIER, "b", 6, 1, 7))),
("red(a=b)", FormatRuleSyntaxError("keyword arguments are not supported", None)),
("red(xy {v})", FormatRuleSyntaxError("Invalid identifier", None)),
("list()", FormatRuleSyntaxError("variable name not found", None)),
("list(recursion_depth=2)", FormatRuleSyntaxError("variable name not found", None)),
("list(a,b,c,d)", FormatRuleSyntaxError("too many positional arguments",
Token(TokenKind.IDENTIFIER, "d", 11, 1, 12))),
("list(a, recursion_depth=hello)", FormatRuleSyntaxError("'hello' is not numeric", None)),
("list(a, recursion_depth='hello')", FormatRuleSyntaxError("'recursion_depth' must be an integer", None)),
])
def test_i_cannot_parse_invalid_format(self, text, expected_error):
parser = FormatRuleParser(text)
parser.parse()
assert parser.error_sink == expected_error
@pytest.mark.parametrize("text", [
"a == 5",
"foo == 5",
"func() == 5",
])
def test_i_can_compile_predicate_when_pure_python(self, text):
sheerka, context, *concepts = self.init_concepts("foo")
service = sheerka.services[SheerkaRuleManager.NAME]
ast_ = ast.parse(text, "<source>", 'eval')
expected_python_node = PythonNode(text, ast_)
res = service.compile_when(context, "test", text)
assert len(res) == 1
assert isinstance(res[0], RulePredicate)
assert res[0].evaluator == PYTHON_EVALUATOR_NAME
assert sheerka.isinstance(res[0].predicate, BuiltinConcepts.RETURN_VALUE)
assert sheerka.objvalue(res[0].predicate) == expected_python_node
assert res[0].concept is None
@pytest.mark.parametrize("text, expected_type", [
("isinstance(a, int)", SourceCodeWithConceptNode),
("func()", SourceCodeNode),
])
def test_i_can_compile_predicates_that_resolve_to_python(self, text, expected_type):
sheerka, context, *concepts = self.init_concepts()
service = sheerka.services[SheerkaRuleManager.NAME]
ast_ = ast.parse(text, "<source>", 'eval')
expected_python_node = PythonNode(text, ast_)
res = service.compile_when(context, "test", text)
assert len(res) == 1
assert isinstance(res[0], RulePredicate)
assert res[0].evaluator == PYTHON_EVALUATOR_NAME
assert sheerka.isinstance(res[0].predicate, BuiltinConcepts.RETURN_VALUE)
assert isinstance(sheerka.objvalue(res[0].predicate), expected_type)
assert sheerka.objvalue(res[0].predicate).python_node == expected_python_node
assert res[0].concept is None
def test_i_can_compile_predicate_when_python_and_concept(self):
sheerka, context, *concepts = self.init_concepts(Concept("foo bar"), create_new=True)
service = sheerka.services[SheerkaRuleManager.NAME]
text = "foo bar == 5"
ast_ = ast.parse("__C__foo0bar__1001__C__ == 5", "<source>", 'eval')
resolved_expected = PythonNode(text, ast_)
res = service.compile_when(context, "test", text)
assert len(res) == 1
assert isinstance(res[0], RulePredicate)
assert res[0].evaluator == PYTHON_EVALUATOR_NAME
assert sheerka.isinstance(res[0].predicate, BuiltinConcepts.RETURN_VALUE)
assert sheerka.objvalue(res[0].predicate) == resolved_expected
assert res[0].concept is None
@pytest.mark.parametrize("text, expected_variables", [
("cat is an animal", ["cat", "animal"]),
("a is an animal", ["a", "animal"]),
("cat is an b", ["cat", "b"]),
])
def test_i_can_compile_predicate_when_exact_concept(self, text, expected_variables):
sheerka, context, *concepts = self.init_concepts(
Concept("x is an y", pre="is_question()", body="isinstance(x, y)").def_var("x").def_var("y"),
Concept("cat"),
Concept("animal"),
create_new=True
)
service = sheerka.services[SheerkaRuleManager.NAME]
expected = concepts[0]
expected.get_metadata().variables = [('x', expected_variables[0]), ('y', expected_variables[1])]
res = service.compile_when(context, "test", text)
assert len(res) == 1
assert isinstance(res[0], RulePredicate)
assert res[0].evaluator == CONCEPT_EVALUATOR_NAME
assert sheerka.isinstance(res[0].predicate, BuiltinConcepts.RETURN_VALUE)
assert sheerka.objvalue(res[0].predicate) == expected
assert res[0].concept == expected
@pytest.mark.parametrize("text, expected_variables", [
("a cat is an animal", ["cat", "animal"]),
("a cat is an b", ["a", "animal"]),
])
def test_i_can_compile_predicate_when_sya_node_parser(self, text, expected_variables):
sheerka, context, *concepts = self.init_concepts(
Concept("x is an y", pre="is_question()", body="isinstance(x, y)").def_var("x").def_var("y"),
Concept("a cat"),
Concept("animal"),
create_new=True
)
service = sheerka.services[SheerkaRuleManager.NAME]
expected = concepts[0]
res = service.compile_when(context, "test", text)
assert len(res) == 1
assert isinstance(res[0], RulePredicate)
assert res[0].evaluator == CONCEPT_EVALUATOR_NAME
assert sheerka.isinstance(res[0].predicate, BuiltinConcepts.RETURN_VALUE)
assert sheerka.objvalue(res[0].predicate)[0].concept == expected
assert res[0].concept == expected
def test_i_can_compile_predicate_when_bnf_node_parser(self):
sheerka, context, *concepts = self.init_concepts(
Concept("animal"),
Concept("x is an y", pre="is_question()", definition="('cat'|'bird')=x 'is an' animal").def_var("x"),
create_new=True
)
service = sheerka.services[SheerkaRuleManager.NAME]
expected = concepts[1]
res = service.compile_when(context, "test", "cat is an animal")
assert len(res) == 1
assert isinstance(res[0], RulePredicate)
assert res[0].evaluator == CONCEPT_EVALUATOR_NAME
assert sheerka.isinstance(res[0].predicate, BuiltinConcepts.RETURN_VALUE)
assert sheerka.objvalue(res[0].predicate)[0].concept == expected
assert res[0].concept == expected
def test_i_can_compile_predicate_when_multiple_choices(self):
sheerka, context, *concepts = self.init_concepts(
Concept("x is a y", pre="is_question()", body="isinstance(x, y)").def_var("x").def_var("y"),
Concept("x is a y", pre="is_question()", body="isa(x, y)").def_var("x").def_var("y"),
create_new=True
)
service = sheerka.services[SheerkaRuleManager.NAME]
res = service.compile_when(context, "test", "a is a b")
assert len(res) == 2
assert isinstance(res[0], RulePredicate)
assert res[0].evaluator == CONCEPT_EVALUATOR_NAME
assert sheerka.isinstance(res[0].predicate, BuiltinConcepts.RETURN_VALUE)
assert sheerka.objvalue(res[0].predicate)[0].concept == concepts[0]
assert res[0].concept == concepts[0]
assert isinstance(res[1], RulePredicate)
assert res[1].evaluator == CONCEPT_EVALUATOR_NAME
assert sheerka.isinstance(res[1].predicate, BuiltinConcepts.RETURN_VALUE)
assert sheerka.objvalue(res[1].predicate)[0].concept == concepts[1]
assert res[1].concept == concepts[1]
# @pytest.mark.skip
# @pytest.mark.parametrize("text, expected", [
# ("cat is an animal", set()),
# ("a is an animal", {"a"}),
# ("a is an b", {"a", "b"}),
# ("cat is an b", {"b"}),
# ("a cat is an b", {"b"}),
#
# ("cat is a animal", set()),
# ("a is a animal", {"a"}),
# ("a is a b", {"a", "b"}),
# ("cat is a b", {"b"}),
# ("a cat is an b", {"b"}),
#
# ("a == 5", {"a"}),
# ("isinstance(a, int)", {"a"}),
# ("a cat == b", {"b"})
# ])
# def test_i_can_get_rules_variables(self, text, expected):
# sheerka, context, *concepts = self.init_concepts(
# Concept("x is a y", pre="is_question()", body="isinstance(x, y)").def_var("x").def_var("y"),
# Concept("x is a y", pre="is_question()", body="isa(x, y)").def_var("x").def_var("y"),
# Concept("x is an y", pre="is_question()", body="isinstance(x, y)").def_var("x").def_var("y"),
# Concept("cat"),
# Concept("animal"),
# Concept("a cat"),
# create_new=True
# )
# service = sheerka.services[SheerkaRuleManager.NAME]
#
# compiled = service.compile_when(context, "test", "a is a b")
#
# assert service.get_unknown_variables(compiled) == expected
class TestSheerkaRuleManagerUsingFileBasedSheerka(TestUsingFileBasedSheerka):
def test_rules_are_initialized_at_startup(self):
sheerka, context, *rules = self.init_format_rules(
Rule("print", "name1", "True", "Hello world"),
Rule("print", "name2", "value() is __EXPLANATION", "list(value())")
)
sheerka.set_is_greater_than(context, BuiltinConcepts.PRECEDENCE,
rules[0],
rules[1],
RULE_COMPARISON_CONTEXT)
sheerka.cache_manager.commit(context)
assert len(sheerka.cache_manager.copy(SheerkaRuleManager.FORMAT_RULE_ENTRY)) == len(rules)
sheerka = self.get_sheerka() # new instance
assert len(sheerka.cache_manager.copy(SheerkaRuleManager.FORMAT_RULE_ENTRY)) == len(rules)
# manually update the rules (I need their new priorities)
service = sheerka.services[SheerkaRuleManager.NAME]
rules = [service.format_rule_cache.get(rule_id) for rule_id in service.format_rule_cache]
# check if the rules are correctly initialized
rules_as_map = {rule.id: rule for rule in rules}
for rule_id in service.format_rule_cache:
actual = service.format_rule_cache.get(rule_id)
expected = rules_as_map[rule_id]
assert actual.metadata.is_compiled == expected.metadata.is_compiled
assert actual.metadata.is_enabled == expected.metadata.is_enabled
assert actual.compiled_action == expected.compiled_action
assert actual.compiled_predicate == expected.compiled_predicate
assert actual.priority is not None
assert actual.priority == expected.priority