Fixed #51 : Added unit tests
This commit is contained in:
+2
-1
@@ -31,7 +31,8 @@ class Rule:
|
|||||||
rule_id=None,
|
rule_id=None,
|
||||||
is_enabled=None):
|
is_enabled=None):
|
||||||
self.metadata = RuleMetadata(action_type, name, predicate, action, id=rule_id, is_enabled=is_enabled)
|
self.metadata = RuleMetadata(action_type, name, predicate, action, id=rule_id, is_enabled=is_enabled)
|
||||||
self.compiled_predicates = None
|
self.compiled_predicates = None # old version (to remove when possible)
|
||||||
|
self.compiled_conditions = None # new version
|
||||||
self.compiled_action = None
|
self.compiled_action = None
|
||||||
from core.sheerka.services.SheerkaComparisonManager import SheerkaComparisonManager
|
from core.sheerka.services.SheerkaComparisonManager import SheerkaComparisonManager
|
||||||
self.priority = priority if priority is not None else SheerkaComparisonManager.DEFAULT_COMPARISON_VALUE
|
self.priority = priority if priority is not None else SheerkaComparisonManager.DEFAULT_COMPARISON_VALUE
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class SheerkaEvaluateRules(BaseService):
|
|||||||
results.setdefault(LOW_PRIORITY_RULES, []).append(rule)
|
results.setdefault(LOW_PRIORITY_RULES, []).append(rule)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
res = self.evaluate_rule(sub_context, rule, bag)
|
res = self.evaluate_rule_old(sub_context, rule, bag)
|
||||||
ok = res.status and self.sheerka.is_success(self.sheerka.objvalue(res))
|
ok = res.status and self.sheerka.is_success(self.sheerka.objvalue(res))
|
||||||
results.setdefault(ok, []).append(rule)
|
results.setdefault(ok, []).append(rule)
|
||||||
if ok and success_priority is None:
|
if ok and success_priority is None:
|
||||||
@@ -96,9 +96,9 @@ class SheerkaEvaluateRules(BaseService):
|
|||||||
sub_context.add_values(rules_result=results)
|
sub_context.add_values(rules_result=results)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def evaluate_rule(self, context, rule, bag):
|
def evaluate_rule_old(self, context, rule, bag):
|
||||||
"""
|
"""
|
||||||
Evaluate all the predicate
|
Evaluate the conditions
|
||||||
:param context:
|
:param context:
|
||||||
:param rule:
|
:param rule:
|
||||||
:param bag:
|
:param bag:
|
||||||
@@ -132,6 +132,47 @@ class SheerkaEvaluateRules(BaseService):
|
|||||||
|
|
||||||
return expect_one(context, results)
|
return expect_one(context, results)
|
||||||
|
|
||||||
|
def evaluate_rule(self, context, rule, bag):
|
||||||
|
"""
|
||||||
|
Evaluate the conditions
|
||||||
|
:param context:
|
||||||
|
:param rule:
|
||||||
|
:param bag:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
bag_variables = set(bag.keys())
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for compiled_condition in rule.compiled_conditions:
|
||||||
|
|
||||||
|
if compiled_condition.variables.intersection(bag_variables) != compiled_condition.variables:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if compiled_condition.return_value is None:
|
||||||
|
# We only want to test the existence of a data
|
||||||
|
results.append(context.sheerka.ret(self.NAME, True, True))
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
# do not forget to reset the 'is_evaluated' in the case of a concept
|
||||||
|
if compiled_condition.evaluator_type == ConceptEvaluator.NAME:
|
||||||
|
compiled_condition.concept.get_metadata().is_evaluated = False
|
||||||
|
|
||||||
|
evaluator = self.evaluators_by_name[compiled_condition.evaluator]
|
||||||
|
res = evaluator.eval(context, compiled_condition.return_value)
|
||||||
|
if res.status and isinstance(res.body, bool) and res.body:
|
||||||
|
# one successful value found. No need to look any further
|
||||||
|
results = [res]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
results.append(res)
|
||||||
|
|
||||||
|
debugger = context.get_debugger(SheerkaEvaluateRules.NAME, "evaluate_rule", new_debug_id=False)
|
||||||
|
debugger.debug_rule(rule, results)
|
||||||
|
|
||||||
|
return expect_one(context, results)
|
||||||
|
|
||||||
def remove_from_rete_memory(self, lst):
|
def remove_from_rete_memory(self, lst):
|
||||||
if lst is None:
|
if lst is None:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -628,6 +628,18 @@ class RuleCompiledPredicate:
|
|||||||
variables: Set[str] = None # TODO: set of required variables
|
variables: Set[str] = None # TODO: set of required variables
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass()
|
||||||
|
class CompiledCondition:
|
||||||
|
"""
|
||||||
|
The 'when' expression is parsed to have a ReturnValueConcept or a Concept that can then be evaluated
|
||||||
|
Depending on the evaluator, the 'predicate' attribute or the 'concept' attribute will be used
|
||||||
|
"""
|
||||||
|
evaluator_type: Union[str, None] # PythonEvaluator.NAME | ConceptEvaluator.NAME
|
||||||
|
return_value: Union[ReturnValueConcept, None] # compiled source as ReturnValue
|
||||||
|
variables: Set[str]
|
||||||
|
concept: Union[Concept, None] = None # compiled source as concept
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CompiledWhenResult:
|
class CompiledWhenResult:
|
||||||
"""
|
"""
|
||||||
@@ -1298,4 +1310,18 @@ class ReteConditionExprVisitor(ExpressionVisitor):
|
|||||||
|
|
||||||
|
|
||||||
class PythonConditionExprVisitor(ExpressionVisitor):
|
class PythonConditionExprVisitor(ExpressionVisitor):
|
||||||
pass
|
def __init__(self, context):
|
||||||
|
self.context = context
|
||||||
|
self.var_counter = 0
|
||||||
|
self.variables = set()
|
||||||
|
|
||||||
|
def get_conditions(self, expr_node):
|
||||||
|
self.var_counter = 0
|
||||||
|
self.variables.clear()
|
||||||
|
|
||||||
|
condition = self.visit(expr_node)
|
||||||
|
return [condition]
|
||||||
|
|
||||||
|
def visit_VariableNode(self, expr_node: VariableNode):
|
||||||
|
# no evaluator to call, simply check that the variable is in the bag
|
||||||
|
return CompiledCondition(None, None, {expr_node.name})
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ from core.concept import Concept, DEFINITION_TYPE_DEF, DoNotResolve
|
|||||||
from core.global_symbols import RULE_COMPARISON_CONTEXT, NotFound, EVENT_RULE_DELETED
|
from core.global_symbols import RULE_COMPARISON_CONTEXT, NotFound, EVENT_RULE_DELETED
|
||||||
from core.rule import Rule, ACTION_TYPE_PRINT, ACTION_TYPE_EXEC
|
from core.rule import Rule, ACTION_TYPE_PRINT, ACTION_TYPE_EXEC
|
||||||
from core.sheerka.Sheerka import RECOGNIZED_BY_ID, RECOGNIZED_BY_NAME
|
from core.sheerka.Sheerka import RECOGNIZED_BY_ID, RECOGNIZED_BY_NAME
|
||||||
|
from core.sheerka.services.SheerkaEvaluateRules import SheerkaEvaluateRules
|
||||||
from core.sheerka.services.SheerkaExecute import ParserInput
|
from core.sheerka.services.SheerkaExecute import ParserInput
|
||||||
from core.sheerka.services.SheerkaRuleManager import SheerkaRuleManager, FormatRuleActionParser, \
|
from core.sheerka.services.SheerkaRuleManager import SheerkaRuleManager, FormatRuleActionParser, \
|
||||||
FormatAstRawText, FormatAstVariable, FormatAstSequence, FormatAstFunction, \
|
FormatAstRawText, FormatAstVariable, FormatAstSequence, FormatAstFunction, \
|
||||||
FormatRuleSyntaxError, FormatAstList, UnexpectedEof, FormatAstColor, RuleCompiledPredicate, FormatAstDict, \
|
FormatRuleSyntaxError, FormatAstList, UnexpectedEof, FormatAstColor, RuleCompiledPredicate, FormatAstDict, \
|
||||||
FormatAstMulti, \
|
FormatAstMulti, \
|
||||||
PythonCodeEmitter, NoConditionFound, FormatAstNode, ReteConditionExprVisitor
|
PythonCodeEmitter, NoConditionFound, FormatAstNode, ReteConditionExprVisitor, PythonConditionExprVisitor, \
|
||||||
|
CompiledCondition
|
||||||
from core.sheerka.services.sheerka_service import FailedToCompileError
|
from core.sheerka.services.sheerka_service import FailedToCompileError
|
||||||
from core.tokenizer import Token, TokenKind
|
from core.tokenizer import Token, TokenKind
|
||||||
from parsers.BaseNodeParser import SourceCodeWithConceptNode, SourceCodeNode
|
from parsers.BaseNodeParser import SourceCodeWithConceptNode, SourceCodeNode
|
||||||
@@ -1240,6 +1242,7 @@ isinstance(var, Concept) and var.key == 'hello __var__0'""" + \
|
|||||||
matches = list(network.matches)
|
matches = list(network.matches)
|
||||||
assert len(matches) == 1
|
assert len(matches) == 1
|
||||||
|
|
||||||
|
@pytest.mark.skip("I am not sure yet of what I want to get")
|
||||||
@pytest.mark.parametrize("expression, expected_as_str", [
|
@pytest.mark.parametrize("expression, expected_as_str", [
|
||||||
(
|
(
|
||||||
"eval(__ret.body, 'foo' starts with 'f')",
|
"eval(__ret.body, 'foo' starts with 'f')",
|
||||||
@@ -1287,6 +1290,74 @@ isinstance(var, Concept) and var.key == 'hello __var__0'""" + \
|
|||||||
matches = list(network.matches)
|
matches = list(network.matches)
|
||||||
assert len(matches) == 1
|
assert len(matches) == 1
|
||||||
|
|
||||||
|
def test_i_can_get_compiled_conditions_when_testing_data_existence(self):
|
||||||
|
sheerka, context = self.init_test().unpack()
|
||||||
|
expression = "__ret"
|
||||||
|
|
||||||
|
parser = ExpressionParser()
|
||||||
|
error_sink = ErrorSink()
|
||||||
|
parser_input = ParserInput(expression)
|
||||||
|
parser.reset_parser_input(parser_input, error_sink)
|
||||||
|
parsed = parser.parse_input(context, parser_input, error_sink)
|
||||||
|
|
||||||
|
visitor = PythonConditionExprVisitor(context)
|
||||||
|
conditions = visitor.get_conditions(parsed)
|
||||||
|
|
||||||
|
assert len(conditions) == 1
|
||||||
|
assert isinstance(conditions[0], CompiledCondition)
|
||||||
|
assert conditions[0].evaluator_type is None
|
||||||
|
assert conditions[0].return_value is None
|
||||||
|
assert conditions[0].concept is None
|
||||||
|
assert conditions[0].variables == {"__ret"}
|
||||||
|
|
||||||
|
# check against SheerkaEvaluateRules
|
||||||
|
evaluate_rules_service = sheerka.services[SheerkaEvaluateRules.NAME]
|
||||||
|
bag = {"__ret": ReturnValueConcept("Test", True, None)}
|
||||||
|
rule = Rule(name="test_i_can_get_compiled_conditions", predicate=expression)
|
||||||
|
rule.compiled_conditions = conditions
|
||||||
|
res = evaluate_rules_service.evaluate_rule(context, rule, bag)
|
||||||
|
assert res.status
|
||||||
|
assert self.sheerka.is_success(self.sheerka.objvalue(res))
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("expression", [
|
||||||
|
"__ret",
|
||||||
|
"__ret.status == True",
|
||||||
|
"__ret.status",
|
||||||
|
"__ret and __ret.status",
|
||||||
|
])
|
||||||
|
def test_i_can_get_compiled_conditions(self, expression):
|
||||||
|
sheerka, context = self.init_test().unpack()
|
||||||
|
|
||||||
|
parser = ExpressionParser()
|
||||||
|
error_sink = ErrorSink()
|
||||||
|
parser_input = ParserInput(expression)
|
||||||
|
parser.reset_parser_input(parser_input, error_sink)
|
||||||
|
parsed = parser.parse_input(context, parser_input, error_sink)
|
||||||
|
|
||||||
|
visitor = PythonConditionExprVisitor(context)
|
||||||
|
conditions = visitor.get_conditions(parsed)
|
||||||
|
|
||||||
|
ast_ = ast.parse(expression, "<source>", 'eval')
|
||||||
|
expected_python_node = PythonNode(expression, ast_)
|
||||||
|
|
||||||
|
assert len(conditions) == 1
|
||||||
|
assert isinstance(conditions[0], CompiledCondition)
|
||||||
|
assert conditions[0].evaluator_type == PYTHON_EVALUATOR_NAME
|
||||||
|
assert sheerka.isinstance(conditions[0].return_value, BuiltinConcepts.RETURN_VALUE)
|
||||||
|
assert sheerka.objvalue(conditions[0].return_value) == expected_python_node
|
||||||
|
assert conditions[0].concept is None
|
||||||
|
assert conditions[0].variables == {"__ret"}
|
||||||
|
|
||||||
|
# check against SheerkaEvaluateRules
|
||||||
|
evaluate_rules_service = sheerka.services[SheerkaEvaluateRules.NAME]
|
||||||
|
bag = {"__ret": ReturnValueConcept("Test", True, None)}
|
||||||
|
rule = Rule(name="test_i_can_get_compiled_conditions", predicate=expression)
|
||||||
|
rule.compiled_conditions = conditions
|
||||||
|
res = evaluate_rules_service.evaluate_rule(context, rule, bag)
|
||||||
|
assert res.status
|
||||||
|
assert self.sheerka.is_success(self.sheerka.objvalue(res))
|
||||||
|
|
||||||
|
|
||||||
class TestSheerkaRuleManagerUsingFileBasedSheerka(TestUsingFileBasedSheerka):
|
class TestSheerkaRuleManagerUsingFileBasedSheerka(TestUsingFileBasedSheerka):
|
||||||
def test_rules_are_initialized_at_startup(self):
|
def test_rules_are_initialized_at_startup(self):
|
||||||
sheerka, context, *rules = self.init_test().with_rules(
|
sheerka, context, *rules = self.init_test().with_rules(
|
||||||
|
|||||||
Reference in New Issue
Block a user