# Проверяет запуск служебного 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") # Показывает независимо подтверждённый результат каждого аппаратного профиля.