"""Проверяет тайм-аут штатного CAN на настоящих функциях проекта 166.""" # Модель не заменяет испытание аппаратного eCAN. import os # Передаёт пути библиотек GCC только дочерним процессам. from pathlib import Path # Находит исходники независимо от рабочего каталога запуска. import re # Удаляет комментарии и выделяет тела проверяемых функций. import subprocess # Запускает компилятор и проверяет поведенческий результат модели. import tempfile # Изолирует сгенерированные файлы от образов прошивки пользователя. import sys # Позволяет проверить отдельно сохранённую старую версию ecan.c. root = Path(__file__).resolve().parents[2] # Корень проекта содержит рабочую реализацию eCAN. source_path = Path(sys.argv[1]) if len(sys.argv) > 1 else root / "Source/Internal/ecan.c" # Дополнительный путь нужен для проверки чувствительности к старой ошибке. source = re.sub(r"/\*.*?\*/|//[^\n]*", "", source_path.read_text(encoding="utf-8"), flags=re.S) # Комментарии со скобками не участвуют в выделении функций. def function(name): # Извлекает текущий алгоритм вместо поддержания отдельной копии в тесте. match = re.search(r"(?:interrupt\s+)?void\s+" + name + r"\([^;{}]*\)\s*\{", source) # Отличает определение от вызова и объявления. assert match is not None, name # Исчезновение функции должно останавливать проверку. end, depth = match.end(), 1 # Начинает поиск после открывающей скобки тела. while depth: # Учитывает вложенные условия до закрытия внешнего блока. depth += (source[end] == "{") - (source[end] == "}") # Считает только скобки кода без комментариев. end += 1 # Включает найденную закрывающую скобку в результат. return source[match.start():end] # Сохраняет все реальные обращения к регистрам и условия. model = Path(__file__).with_name("model.inc").read_text(encoding="utf-8") # Загружает минимальное аппаратное окружение и сценарии потери ACK. model = model.replace("/* PRODUCTION */", function("CAN_send") + "\n" + function("CANa_reset_err")) # Проверяет передачу и прерывание в одном жизненном цикле. compiler = os.environ.get("CC", "C:/msys64/mingw64/bin/gcc.exe") # Использует установленный GCC, сохраняя возможность явно выбрать другой. environment = dict(os.environ) # Не меняет окружение приложения и проекта CCS. environment["PATH"] = str(Path(compiler).parent) + os.pathsep + environment.get("PATH", "") # Делает библиотеки MinGW доступными тестовой программе. with tempfile.TemporaryDirectory(prefix="can-timeout-") as directory: # Удаляет только собственные временные файлы после завершения проверки. c_path, exe_path = Path(directory) / "test.c", Path(directory) / "test.exe" # Разделяет исходник модели и исполняемый результат. c_path.write_text(model, encoding="utf-8") # Сохраняет русские пояснения модели в требуемой кодировке. subprocess.run([compiler, "-std=c99", "-O3", "-Wall", "-Wextra", "-Wno-unused-parameter", "-Werror", str(c_path), "-o", str(exe_path)], env=environment, check=True) # Проверяет реальные функции под оптимизацией и без скрытых предупреждений. subprocess.run([str(exe_path)], env=environment, check=True, timeout=10) # Зависание и нарушенное условие делают сценарий неуспешным.