# Проверяет передачу фаз и диагностику на настоящих функциях measure.c без аппаратуры C28x. import argparse # Принимает путь к компилятору хостовой модели, не привязывая тест к одной машине. import os # Добавляет каталог компилятора в PATH для запуска его вспомогательных программ. from pathlib import Path # Разрешает пути относительно теста независимо от рабочего каталога. import re # Находит границы функций и единственное место исторической мутации. import subprocess # Компилирует модель и проверяет коды завершения исполняемых проверок. import tempfile # Размещает сгенерированный C и исполняемые файлы вне исходного дерева. ROOT = Path(__file__).resolve().parents[2] # Поднимается от tests/current_measure к корню репозитория. SOURCE = ROOT / "Source/Internal/measure.c" # Выбирает текущую production-реализацию измерений. def extract(source, name): # Извлекает одну настоящую функцию, чтобы тест не содержал копию алгоритма. match = re.search(r"(?:void|int|float)\s+" + name + r"\([^;{}]*\)\s*\{", source) # Отличает определение от прототипа и вызовов. assert match is not None, name # Явно завершает тест, если production-функция исчезла или сменила форму. end = source.index("{", match.start()) + 1 # Начинает подсчёт вложенности сразу после открывающей скобки. depth = 1 # Учитывает уже найденный внешний блок функции. while depth: # Читает определение целиком вместе с вложенными условиями. depth += (source[end] == "{") - (source[end] == "}") # Находит соответствующую закрывающую скобку без обрезки вложенных блоков. end += 1 # Включает последнюю обработанную скобку в извлекаемый диапазон. return source[match.start():end] # Возвращает production-код без изменения его вычислений. parser = argparse.ArgumentParser(description="Проверка фаз COMM и LOAD на хосте") # Описывает назначение самостоятельного запуска. parser.add_argument("--cc", required=True, help="Путь к GCC") # Позволяет выбрать доступный C-компилятор без изменения теста. args = parser.parse_args() # Разбирает аргументы до создания временных файлов. source = re.sub(r"/\*.*?\*/|//[^\n]*", "", SOURCE.read_text(encoding="utf-8"), flags=re.S) # Убирает комментарии с примерами скобок, сохраняя исполняемый код. body = "\n".join(extract(source, name) for name in ("im_calc", "er_anal", "reset_errs", "Current_count")) # Включает реальные расчёты, накопление ошибки и запись диагностических флагов. before, after = Path(__file__).with_name("model.inc").read_text(encoding="utf-8").split("/* HANDLERS */") # Вставляет production-функции между моделью окружения и сценариями. legacy, replacements = re.subn(r"\bpair\s*=\s*chan\s*\^\s*1\s*;", "pair = sens_pair[chan];", body) # Возвращает старую ошибку только во временную мутационную сборку. assert replacements == 1, "Не найдено единственное место выбора пары фаз" # Не допускает фиктивной мутационной проверки неизменённого кода. environment = dict(os.environ) # Сохраняет окружение вызывающего процесса для компилятора. environment["PATH"] = str(Path(args.cc).resolve().parent) + os.pathsep + environment.get("PATH", "") # Делает библиотеки и вспомогательные программы GCC доступными дочерним процессам. with tempfile.TemporaryDirectory(prefix="current-measure-") as directory: # Автоматически удаляет только временные артефакты этого запуска. for name, functions in (("production", body), ("legacy", legacy)): # Прогоняет одинаковые поведенческие сценарии на исправлении и исторической ошибке. c_path = Path(directory) / (name + ".c") # Разделяет исходники двух сборок для однозначных диагностик компилятора. exe_path = Path(directory) / (name + ".exe") # Даёт каждой модели собственный исполняемый файл. c_path.write_text(before + functions + after, encoding="utf-8") # Сохраняет временный C с русскими пояснениями в UTF-8. subprocess.run([args.cc, "-std=c99", "-O3", "-fno-strict-aliasing", "-Werror=implicit-function-declaration", "-I", str(ROOT / "Source/Internal/Include"), str(c_path), "-lm", "-o", str(exe_path)], check=True, env=environment) # Компилирует с O3; отключение alias-оптимизации сохраняет production-псевдонимы ERROR поверх Modbus на хосте. result = subprocess.run([str(exe_path)], capture_output=True, text=True, env=environment, timeout=20) # Ограничивает зависание и сохраняет результат каждого поведенческого сценария. if name == "production": # Исправленная реализация обязана пройти все проверки без исключений. print(result.stdout, end="") # Показывает сценарии, успешно проверенные настоящими функциями прошивки. assert result.returncode == 0, result.stdout + result.stderr # Возвращает сбой запуска при любой регрессии исправленного кода. else: # Старая строка должна воспроизводимо ломать и значения фаз, и диагностику. assert result.returncode == 1 and "FAIL Modbus" in result.stdout and "FAIL Wry" in result.stdout, result.stdout + result.stderr # Доказывает чувствительность теста к обоим последствиям именно этой ошибки. print("PASS mutation: legacy pair rejected by Modbus and Wry checks") # Сообщает об ожидаемом провале временной сборки со старой строкой.