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}")