"""Проверяет TESTLAMP на настоящих функциях прошивки без подключения платы.""" import argparse # Позволяет использовать доступный GCC для модели аппаратного окружения. import os # Передаёт компилятору PATH с его вспомогательными программами и библиотеками. from pathlib import Path # Разрешает исходники и модель относительно каталога этого теста. import re # Извлекает функции, убирая комментарии, которые могут содержать примеры скобок. import subprocess # Компилирует и запускает независимые проверки с контролем результата. import tempfile # Удаляет только временные исходники и исполняемые файлы данного запуска. ROOT = Path(__file__).resolve().parents[2] # Получает корень репозитория из расположения tests/testlamp. def source(name): # Читает production-модуль без изменения исходного файла. data = (ROOT / "Source/Internal" / name).read_bytes() # Читает исходник без изменения его кодировки: в 166 ещё есть модули Windows-1251. try: # Сначала использует UTF-8 для перенесённых модулей индикации. text = data.decode("utf-8") # Строго декодирует обновлённые исходники без потери русских комментариев. except UnicodeDecodeError: # Поддерживает старый measure.c, не изменяя его ради хостового теста. text = data.decode("cp1251") # Читает исходную Windows-1251 только при невозможности декодирования UTF-8. return re.sub(r"/\*.*?\*/|//[^\n]*", "", text, flags=re.S) # Убирает комментарии перед разбором вложенных блоков. def function(text, name): # Выбирает определение функции вместо копии её алгоритма в тесте. match = re.search(r"(?:interrupt\s+)?(?:void|int)\s+" + name + r"\([^;{}]*\)\s*\{", text) # Отличает тело функции от прототипов и вызовов. assert match is not None, name # Завершает проверку, если проверяемая production-функция исчезла. end, depth = match.end(), 1 # Учитывает найденную открывающую скобку внешнего блока. while depth: # Читает все вложенные ветви до конца определения. depth += (text[end] == "{") - (text[end] == "}") # Находит парную закрывающую скобку. end += 1 # Включает текущий символ в извлекаемый фрагмент. return text[match.start():end] # Сохраняет порядок операторов настоящей функции. parser = argparse.ArgumentParser(description=__doc__) # Объясняет назначение отдельного запуска проверки. parser.add_argument("--cc", default="C:/msys64/mingw64/bin/gcc.exe", help="Путь к GCC") # Повторяет установленный в этом проекте хостовый компилятор. args = parser.parse_args() # Разбирает параметры до формирования временных файлов. button = function(source("peripher.c"), "get_Buttons") # Проверяет именно публикацию отфильтрованной кнопки в Modbus. pult = source("pulto.c") # Берёт текущую обработку индикации, включая реальные ветви теста и яркости. body = "\n".join((function(source("measure.c"), "er_anal"), button, function(pult, "what_is"), function(pult, "cpu_timer1_isr_PULT"))) # Собирает функции, участвующие в переключении режима и передаче масок. before, after = Path(__file__).with_name("model.inc").read_text(encoding="utf-8").split("/* HANDLERS */") # Помещает production-функции между аппаратными заглушками и поведенческими сценариями. old_button, count_button = re.subn(r"(er_anal\(GpioDataRegs\.GPADAT\.bit\.GPIO26,\s*&INcount0,\s*1000,\s*)bTestLamp(\))", r"\g<1>0\2", body) # Возвращает сброс фильтра при одном провале только во временную мутацию. old_mask, count_mask = re.subn(r"IER\s*&=\s*\(MINT13\s*&\s*~M_INT13\);", "IER |= MINT13;", body) # Восстанавливает ошибочное разрешение прерываний в отдельном временном варианте. assert count_button == count_mask == 1, "Не найдены единственные места исправлений TESTLAMP" # Не позволяет мутации незаметно проверять неизменённый код. environment = dict(os.environ) # Сохраняет окружение основного процесса. environment["PATH"] = str(Path(args.cc).resolve().parent) + os.pathsep + environment.get("PATH", "") # Делает DLL и вспомогательные программы GCC доступными дочерним процессам. with tempfile.TemporaryDirectory(prefix="testlamp-") as directory: # Изолирует артефакты каждой проверки от дерева прошивки. for name, functions, failure in (("production", body, None), ("old_button", old_button, "FAIL held test"), ("old_mask", old_mask, "FAIL interrupt mask")): # Проверяет исправление и чувствительность сценариев к каждой из двух старых ошибок. c_path, exe_path = Path(directory) / (name + ".c"), Path(directory) / (name + ".exe") # Разделяет временные исходники и программы трёх вариантов. c_path.write_text(before + functions + after, encoding="utf-8") # Сохраняет модель и русские пояснения в UTF-8. subprocess.run([args.cc, "-std=c99", "-O3", "-fno-strict-aliasing", "-Werror=implicit-function-declaration", "-I", str(ROOT / "Source/Internal/Include"), "-I", str(ROOT / "Source/External/v120/DSP2833x_common/include"), str(c_path), "-o", str(exe_path)], env=environment, check=True) # Сохраняет адреса production-псевдонимов регистров и использует реальную таблицу приоритетов TI. result = subprocess.run([str(exe_path)], env=environment, capture_output=True, text=True, timeout=20) # Ограничивает зависание и сохраняет конкретные нарушенные условия. if failure is None: # Рабочая версия обязана пройти все сценарии. print(result.stdout, end="") # Показывает результат поведенческих проверок текущей прошивки. assert result.returncode == 0, result.stdout + result.stderr # Возвращает ошибку при любой регрессии TESTLAMP или рабочего режима. else: # Старая ошибка должна воспроизводимо нарушить соответствующее требование. assert result.returncode == 1 and failure in result.stdout, result.stdout + result.stderr # Доказывает, что тест действительно обнаруживает возвращённый дефект. print("PASS mutation:", name, "rejected") # Сообщает об ожидаемом провале временного варианта со старой ошибкой.