Добавить CAN heartbeat и передачу по изменению с повтором через 1,3 с, компактный логгер v4, паспорт прошивки и блочное чтение EEPROM. Подключить автоматическую версию и проверяемую сборку BIN. Сохранить локальные исправления стробов и защиты Timer1. Обновить тесты, документацию, UTF-8 и исключения временных файлов. Проверки: CAN, R3, EEPROM, паспорт, TESTLAMP, логгер, START/STOP; 9 сценариев сборки, включая реальную конверсию TI. Все 17 внутренних модулей скомпилированы TI C2000 без ошибок.
52 lines
8.3 KiB
Python
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") # Сообщает об ожидаемом провале временного варианта со старой ошибкой.
|