Files
ad_toolbox/unittests.py

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