Files
Balsam_167/tests/testlamp/run_test.py
T
Andrey 52683164af Перенести улучшения прошивки в ветку 166
Добавить CAN heartbeat и передачу по изменению с повтором через 1,3 с,
компактный логгер v4, паспорт прошивки и блочное чтение EEPROM.
Подключить автоматическую версию и проверяемую сборку BIN.
Сохранить локальные исправления стробов и защиты Timer1.
Обновить тесты, документацию, UTF-8 и исключения временных файлов.

Проверки: CAN, R3, EEPROM, паспорт, TESTLAMP, логгер, START/STOP;
9 сценариев сборки, включая реальную конверсию TI.
Все 17 внутренних модулей скомпилированы TI C2000 без ошибок.
2026-10-19 13:46:29 +03:00

52 lines
8.3 KiB
Python

"""Проверяет 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"test_lamp_hold_ticks >= 2U \* \(unsigned int\)READY_FREQ", "test_lamp_hold_ticks >= 1U", body) # Мутация включает TESTLAMP после одного такта вместо двух секунд и должна нарушить сценарий короткого нажатия.
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 short test"), ("old_mask", old_mask, "FAIL interrupt mask")): # Проверяет выдержку кнопки и чувствительность к повторному разрешению Timer1 во время передачи индикации.
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") # Сообщает об ожидаемом провале временного варианта со старой ошибкой.