159 lines
6.2 KiB
Python
159 lines
6.2 KiB
Python
import smartcondition
|
|
import os
|
|
import yaml
|
|
from expressionparser import ParsingException
|
|
|
|
UNIT_TEST_PATH = "unit_tests"
|
|
|
|
class AppDaemonMokup:
|
|
def __init__(self,fake_entitites):
|
|
self.entities = fake_entitites
|
|
|
|
def log(self,message,**kwargs):
|
|
error_level = f"[{kwargs['level']}]" if 'level' in kwargs else ""
|
|
print(f'{error_level}{message}')
|
|
|
|
def get_state(self,entity_id,attribute = None):
|
|
key = f"{entity_id}.{attribute}" if attribute != None else entity_id
|
|
|
|
return self.entities[key] if key in self.entities else None
|
|
|
|
def entity_exists(self,entity_id):
|
|
return entity_id in self.entities
|
|
|
|
#added a couple of method to be directly compatible with Condition Parser
|
|
def add_variable(self,variable_name): return self.entity_exists(variable_name)
|
|
|
|
def get_variable_value(self,variable_name): return self.get_state(variable_name)
|
|
|
|
def reset(self): pass
|
|
|
|
def execute_unit_test(test_file):
|
|
failed_test = dict()
|
|
|
|
with open(test_file, 'r') as file:
|
|
test_config = yaml.safe_load(file)
|
|
if not 'fake_entities' in test_config:
|
|
print(f"Can't run test {test_file} fake_entities is not defined")
|
|
return False
|
|
else:
|
|
print(f"\nRunning test from {test_file}")
|
|
|
|
constants = test_config['constants'] if 'constants' in test_config else dict()
|
|
templates_library = test_config['templates_library'] if 'templates_library' in test_config else None
|
|
appdaemon = AppDaemonMokup(test_config['fake_entities'])
|
|
|
|
test_expression = None
|
|
#test_expression = "binary_sensor.on == not binary_sensor.false"
|
|
|
|
if test_expression:
|
|
condition_parser = smartcondition.ConditionsParser(test_expression,appdaemon)
|
|
print(f"{condition_parser.parsed_data}")
|
|
result = condition_parser.evaluate()
|
|
|
|
for test in test_config['tests']:
|
|
try: conditions = smartcondition.Evaluator(appdaemon,test_config['tests'][test]['conditions'], constants = constants, log_init = False, log_no_valid_condition_details = True, templates_library = templates_library)
|
|
except ParsingException as e:
|
|
success = test_config['tests'][test]['expected_result'] == "ParsingException"
|
|
|
|
print(f"{test}: {'Success' if success else 'Failed'}")
|
|
if not success:
|
|
print(f"{os.path.basename(test_file)} {test}: 'Parsing failed' with Exception {e}")
|
|
failed_test[test] = test_config['tests'][test]
|
|
print()
|
|
continue
|
|
|
|
else:
|
|
if 'expected_entities_to_listen' in test_config['tests'][test]:
|
|
expected_entities_to_listen = test_config['tests'][test]['expected_entities_to_listen']
|
|
entities_to_listen = conditions.get_entities_to_listen_as_list()
|
|
if sorted(expected_entities_to_listen) != sorted(entities_to_listen):
|
|
failed_test[test] = test_config['tests'][test]
|
|
print(f"{test}: Failed")
|
|
print(f"entities_to_listen = {entities_to_listen}, expected : {expected_entities_to_listen}")
|
|
continue
|
|
|
|
|
|
result = conditions.evaluate(False)
|
|
success = result.name == test_config['tests'][test]['expected_result']
|
|
print(f"{test}: {'Success' if success else 'Failed'}")
|
|
if not success:
|
|
failed_test[test] = test_config['tests'][test]
|
|
conditions.log_evaluation_result()
|
|
print()
|
|
continue
|
|
|
|
|
|
print()
|
|
if len(failed_test) > 0:
|
|
print(f"{len(failed_test)} tests have failed in {os.path.basename(test_file)} :")
|
|
for test in failed_test:
|
|
print(f"{test} : {test_config['tests'][test]['conditions']}")
|
|
print(f"Expected result was {test_config['tests'][test]['expected_result']}")
|
|
else:
|
|
print("All the tests have succeeded")
|
|
|
|
|
|
return len(failed_test) == 0
|
|
|
|
def tiny_parser():
|
|
import pyparsing
|
|
|
|
variable_names = pyparsing.Combine(pyparsing.Literal('$') + pyparsing.Word(pyparsing.alphanums + '_'))
|
|
|
|
#integer = pyparsing.Word(pyparsing.nums)
|
|
integer = pyparsing.pyparsing_common.signed_integer
|
|
|
|
double = pyparsing.Combine(pyparsing.Word(pyparsing.nums) + '.' + pyparsing.Word(pyparsing.nums))
|
|
|
|
parser = pyparsing.infix_notation(
|
|
variable_names | double | integer,
|
|
[
|
|
('not', 1, pyparsing.opAssoc.RIGHT),
|
|
('**', 2, pyparsing.opAssoc.RIGHT),
|
|
('-', 1, pyparsing.opAssoc.RIGHT),
|
|
(pyparsing.oneOf('* / // %'), 2, pyparsing.opAssoc.LEFT),
|
|
(pyparsing.oneOf('+ -'), 2, pyparsing.opAssoc.LEFT),
|
|
(pyparsing.oneOf('> >= < <= == !='), 2, pyparsing.opAssoc.LEFT),
|
|
('and', 2, pyparsing.opAssoc.LEFT),
|
|
('or', 2, pyparsing.opAssoc.LEFT)
|
|
]
|
|
)
|
|
|
|
examples = [
|
|
"$true and not 10 == 9",
|
|
"$true == $false",
|
|
"$true == not $false",
|
|
"$true == (not $false)",
|
|
"5 * 10 ** -2",
|
|
"5 * 10 * -2",
|
|
"5 * 10 ** (-2)",
|
|
"5 * -10 ** 2",
|
|
"5 * (-10) ** 2",
|
|
"5 and not 8",
|
|
"5 and -8",
|
|
"1 ** -2",
|
|
"-1 ** 2",
|
|
]
|
|
|
|
longest = max(map(len, examples))
|
|
|
|
for ex in examples:
|
|
result = parser.parseString(ex)
|
|
print(f'{ex:{longest}} <=> {result}')
|
|
|
|
|
|
#tiny_parser()
|
|
failed_test = list()
|
|
|
|
path = os.path.join(os.path.dirname(os.path.abspath(__file__)),UNIT_TEST_PATH)
|
|
with os.scandir(path) as it:
|
|
for entry in it:
|
|
if entry.is_file() and entry.name.lower().endswith('.yaml'):
|
|
if not execute_unit_test(os.path.join(path , entry.name)):
|
|
failed_test.append(entry.name)
|
|
|
|
if len(failed_test):
|
|
print(f"{len(failed_test)} test file(s) have failed :")
|
|
for test in failed_test:
|
|
print(f"{test}") |