Сохранить состояние TESTLAMP при кратких провалах входа и исключить повторный вход Timer1 во время передачи индикации. Явно сбрасывать служебный обмен CAN при запуске и выбирать соседнюю фазу по локальному индексу канала АЦП. Сохранить текущую последовательность DATA/CLK и проверочные варианты таймингов, добавить поведенческие тесты и обновить образ прошивки с картой памяти.
57 lines
10 KiB
Python
57 lines
10 KiB
Python
# Проверяет запуск служебного CAN на грязной RAM: использует рабочие обработчики и канонический упаковщик протокола.
|
||
import os # Читает пути общей библиотеки и компилятора, не меняя настройки проекта.
|
||
from pathlib import Path # Строит абсолютные пути независимо от каталога запуска проверки.
|
||
import re # Извлекает рабочие функции, не заменяя их алгоритмы копиями в модели.
|
||
import shutil # Находит доступный компилятор GCC по пользовательскому PATH.
|
||
import subprocess # Запускает компиляцию и отдельный процесс проверки с контролем кода завершения.
|
||
import sys # Повторно использует текущий Python для загрузки тестовой библиотеки.
|
||
import tempfile # Изолирует сгенерированный код и библиотеку от исходников и образов прошивки.
|
||
|
||
root = Path(__file__).resolve().parents[2] # Находит корень репозитория по расположению постоянного теста.
|
||
templates = Path(os.environ.get("TEMPLATES_ROOT", root.parent / "newProject/SETGUI/third_party/templates")) # Повторяет настройку общей библиотеки существующих тестов УМП.
|
||
protocol = templates / "c/set-protocol" # Выбирает канонические проверку запросов и формат фрагментов CAN.
|
||
compiler = os.environ.get("CC") or shutil.which("gcc") or "C:/msys64/mingw64/bin/gcc.exe" # Поддерживает заданный CC и установленный в рабочем окружении MinGW.
|
||
environment = os.environ.copy() # Ограничивает изменение PATH процессами компилятора и модели.
|
||
environment["PATH"] = str(Path(compiler).resolve().parent) + os.pathsep + environment.get("PATH", "") # Делает доступными вспомогательные DLL выбранного компилятора в Windows.
|
||
|
||
def function(text, name): # Возвращает полное тело указанной функции вместе с её рабочими комментариями.
|
||
match = re.search(r"^(?:static\s+)?(?:void|int)\s+" + re.escape(name) + r"\([^;{}]*\)", text, re.M) # Отличает определение функции от вызова и прототипа.
|
||
if not match: # Не позволяет тесту незаметно проверять устаревшую копию после переименования функции.
|
||
raise AssertionError(f"Не найдена рабочая функция {name}") # Завершает проверку с названием отсутствующей точки интеграции.
|
||
end = re.search(r"^}", text[match.end():], re.M) # Использует принятое в модуле размещение закрывающей скобки функции в первом столбце.
|
||
if not end: # Отвергает неполный фрагмент вместо генерации частично работающей модели.
|
||
raise AssertionError(f"Не найден конец функции {name}") # Указывает причину невозможности извлечения рабочего алгоритма.
|
||
return text[match.start():match.end() + end.end()] + "\n" # Сохраняет исходный порядок операторов для компиляции под O3.
|
||
|
||
source = (root / "Source/Internal/ecan.c").read_text(encoding="utf-8") # Читает именно текущие исходники транспорта, включая новый сброс состояния.
|
||
startup = function(source, "InitCan") # Проверяет реальную связь сброса с аппаратной инициализацией, а не только отдельный helper.
|
||
startup_code = re.sub(r"/\*.*?\*/|//[^\n]*", "", startup, flags=re.S) # Удаляет комментарии, чтобы упоминания helper или регистров не выдавались за вызов.
|
||
assert len(re.findall(r"\bump_can_init\s*\(\s*\)\s*;", startup_code)) == 1, "InitCan должен один раз вызывать ump_can_init" # Требует выполнения нового сброса при каждом аппаратном запуске.
|
||
hardware_start = re.search(r"\b(?:EALLOW|GpioCtrlRegs|ECanaRegs|PieCtrlRegs|IER)\b", startup_code) # Находит первую настройку периферии или разрешение CAN-прерываний.
|
||
assert hardware_start and startup_code.index("ump_can_init()") < hardware_start.start(), "Сброс CAN должен предшествовать настройке периферии" # Не допускает окна прерывания с неочищенными полями обмена.
|
||
state_start = source.index("static volatile unsigned int ump_can_pending;") # Начинает извлечение общих между ISR и главным циклом переменных.
|
||
state_end = source.index("static unsigned long ump_can_sent_at;") + len("static unsigned long ump_can_sent_at;") # Ограничивает фрагмент управляющими полями и штатным буфером ответа.
|
||
state = source[state_start:state_end] # Использует объявления из рабочего модуля, чтобы тест замечал изменение состава состояния.
|
||
state = state.replace("static unsigned int ump_can_reply[", "static uint16_t ump_can_reply[") # Воспроизводит на ПК 16-битный unsigned int C28x именно в буфере слов CAN.
|
||
model = (Path(__file__).with_name("model.inc")).read_text(encoding="utf-8") # Читает аппаратную модель и поведенческие сценарии, сохраняемые рядом с запускателем.
|
||
body = state + "\n" + "".join(function(source, name) for name in ("ump_can_init", "Handlai", "UMP_CAN_service")) # Подставляет сброс, настоящий приём запроса и сервис отправки без изменений их логики.
|
||
assert model.count("/* PRODUCTION_CAN */") == 1, "Нужна единственная точка подключения рабочего кода" # Исключает пропуск или двойную компиляцию проверяемых функций.
|
||
model = model.replace("/* PRODUCTION_CAN */", body) # Встраивает текущие обработчики между аппаратной моделью и сценариями проверки.
|
||
|
||
with tempfile.TemporaryDirectory(prefix="ump-can-init-") as directory: # Автоматически удаляет только временные результаты этой проверки.
|
||
build = Path(directory) # Фиксирует каталог файлов, не затрагивающих бинарные артефакты репозитория.
|
||
source_file = build / "can_init_test.c" # Выделяет общий сгенерированный исходник для обоих профилей EEPROM.
|
||
source_file.write_text(model, encoding="utf-8") # Сохраняет русские комментарии и извлечённые исходники без потери кодировки.
|
||
for profile in (0, 1): # Проверяет обе ветви маршрутизации CAN: AT25256B и M95M02.
|
||
library = build / (f"can_init_{profile}.dll" if os.name == "nt" else f"can_init_{profile}.so") # Разделяет библиотеки, чтобы профиль не подменял результат соседней сборки.
|
||
command = [compiler, "-O3", "-shared", "-Wall", "-Wextra", "-Werror", f"-DEEPROM_M95M02={profile}"] # Проверяет рабочий код с оптимизацией O3 и запрещает скрывать предупреждения модели.
|
||
command.append("-Wno-misleading-indentation") # Допускает существующие смешанные отступы Handlai без изменения проверяемого исходника TI.
|
||
if os.name != "nt": # Учитывает требования загрузки разделяемых библиотек ELF на Linux.
|
||
command.append("-fPIC") # Генерирует переносимые адреса только там, где они нужны формату библиотеки.
|
||
command += ["-I" + str(protocol / "ports/tms320f28335"), str(source_file)] # Передаёт модели официальные объявления порта C28x и текущий извлечённый код.
|
||
command += [str(protocol / "src/ump_protocol.c"), str(protocol / "ports/tms320f28335/ump_tms28335.c"), "-o", str(library)] # Линкует настоящие валидатор и упаковщик фрагментов вместо их заглушек.
|
||
subprocess.run(command, cwd=root, env=environment, check=True) # Прерывает проверку при ошибке любого профиля до объявления успешного результата.
|
||
runner = "import ctypes,sys; result=ctypes.CDLL(sys.argv[1]).run_tests(); print('PASS' if result==0 else f'FAIL: C line {result}'); sys.exit(bool(result))" # Возвращает строку неуспешной проверки C без зависимости от assert и режима NDEBUG.
|
||
subprocess.run([sys.executable, "-c", runner, str(library)], cwd=root, env=environment, check=True) # Освобождает DLL завершением дочернего процесса перед очисткой временного каталога Windows.
|
||
print(f"EEPROM_M95M02={profile}: холодный старт и первый запрос CAN проверены при -O3") # Показывает независимо подтверждённый результат каждого аппаратного профиля.
|