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

45 lines
7.3 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.
# Проверяет передачу фаз и диагностику на настоящих функциях 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") # Сообщает об ожидаемом провале временной сборки со старой строкой.