Files
Balsam_167/tests/ump_can_init/run_test.py
Andrey 04a9d989fb Исправить TESTLAMP, запуск CAN и расчёт фазных токов
Сохранить состояние TESTLAMP при кратких провалах входа и исключить повторный вход Timer1 во время передачи индикации.
Явно сбрасывать служебный обмен CAN при запуске и выбирать соседнюю фазу по локальному индексу канала АЦП.
Сохранить текущую последовательность DATA/CLK и проверочные варианты таймингов, добавить поведенческие тесты и обновить образ прошивки с картой памяти.
2026-10-13 14:13:43 +03:00

57 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Проверяет запуск служебного 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") # Показывает независимо подтверждённый результат каждого аппаратного профиля.