Files
templates/c/firmware-info/tests/test_python37_compatibility.py
T
Andrey 7b1a16d6e4 Исправить сборку CCS и Keil на Python 3.7
Убрать несовместимый параметр missing_ok при очистке временных файлов.
Обеспечить повторное копирование архивов без параметра dirs_exist_ok.
Добавить проверки генерации заголовков и экспорта с API Python 3.7.
2026-11-10 15:13:35 +03:00

67 lines
9.0 KiB
Python

"""Проверки CCS/Keil с интерфейсами pathlib и shutil, доступными в Python 3.7.""" # Воспроизводит ограничения старого Python без установки другого интерпретатора.
import importlib.util # Загружает проверяемые инструменты именно из этой библиотеки templates.
import json # Проверяет сохранённый счётчик и фактический паспорт компиляции.
from pathlib import Path # Моделирует прежнюю сигнатуру удаления временных файлов.
import shutil # Моделирует прежнюю сигнатуру копирования каталогов.
import tempfile # Ограничивает запись изолированным каталогом теста.
import unittest # Проверки запускаются стандартным Python без сторонних зависимостей.
from unittest.mock import patch # Подменяет только несовместимые сигнатуры стандартной библиотеки.
def load_tool(name): # Импортирует одну точку входа без запуска CLI.
spec = importlib.util.spec_from_file_location("compatibility_" + name, Path(__file__).resolve().parents[1] / "tools" / (name + ".py")) # Выбор не зависит от PATH компьютера сборки.
module = importlib.util.module_from_spec(spec) # Отдельный экземпляр сохраняет изоляцию других тестов.
spec.loader.exec_module(module) # Загружает публичные функции подготовки и копирования.
return module # Тесты вызывают те же функции, что BAT-обвязки двух IDE.
identity, c2000 = load_tool("identity"), load_tool("c2000_build") # Обе цепочки используют общий формат паспорта.
original_unlink, original_copytree = Path.unlink, shutil.copytree # Сохраняет настоящие реализации перед ограничением их сигнатур.
def unlink37(path): # Python 3.7 не допускает аргумент missing_ok.
return original_unlink(path) # Реальное удаление сохраняет настоящие ошибки доступа и отсутствующего файла.
def copytree37(source, destination, symlinks=False, ignore=None, copy_function=shutil.copy2, ignore_dangling_symlinks=False): # Сигнатура Python 3.7 не допускает dirs_exist_ok.
return original_copytree(source, destination, symlinks, ignore, copy_function, ignore_dangling_symlinks) # Старый интерфейс продолжает копировать обычные новые каталоги.
class Python37CompatibilityTests(unittest.TestCase): # Проверяет успешный prebuild и повторный экспорт вместо одного синтаксического анализа.
def setUp(self): # Каждый сценарий начинает с отдельного проекта со включённым итератором версии.
temporary = tempfile.TemporaryDirectory(prefix="firmware python37 ") # Пробелы в пути соответствуют обычным пользовательским библиотекам.
self.addCleanup(temporary.cleanup) # Очистка выполняется после снятия ограничений стандартной библиотеки.
self.root = Path(temporary.name) # Рабочие проекты CCS/Keil не изменяются.
self.config = self.root / "firmware_identity.json" # Конфигурация принадлежит только временной проверке.
self.config.write_text(json.dumps({"product": "TEST_BOARD", "target": "TMS320F2812", "version": [1, 2, 3], "increment_version": True}), encoding="utf-8") # Этот режим воспроизводит исходную ошибку unlink после увеличения версии.
def test_keil_and_ccs_prepare_generate_headers_with_old_unlink(self): # Обе обвязки должны завершить генерацию при старой сигнатуре pathlib.
with patch.object(Path, "unlink", unlink37): # Любой оставшийся missing_ok вызовет тот же TypeError, что в журнале пользователя.
keil = identity.prepare(self.root, self.root, self.config, self.root / "Generated/target_0") # Keil напрямую вызывает общий prepare.
ccs = c2000.prepare(self.root, Path(__file__).resolve().parents[3], self.root / "STEND40_PCH_1/generated", config=self.config) # CCS загружает генератор из полной проверяемой библиотеки templates.
for relative, expected in (("Generated/target_0", [1, 2, 4]), ("STEND40_PCH_1/generated", [1, 2, 5])): # Две попытки резервируют разные номера без пропуска после TypeError.
output = self.root / relative # Проверяет фактический include-каталог каждой IDE.
self.assertIn("#define FIRMWARE_VERSION_PATCH " + str(expected[2]) + "U", (output / "firmware_identity.h").read_text(encoding="utf-8")) # Заголовок существует и содержит зарезервированную версию.
self.assertEqual(json.loads((output / "firmware_identity.json").read_text(encoding="utf-8"))["version"], expected) # Паспорт совпадает с константами для компилятора.
self.assertEqual((keil["version"], ccs["version"]), ([1, 2, 4], [1, 2, 5])) # Обе публичные функции возвращают успешный результат.
def test_archive_export_and_atomic_copy_support_old_file_apis(self): # Проверяет повторное сохранение комплекта после успешного prebuild.
generated = self.root / "generated" # Метаданные сохраняются отдельно от исходного JSON.
identity.prepare(self.root, self.root, self.config, generated) # Получает настоящий паспорт для проверки SHA-256 и архива.
image = self.root / "application.bin" # Произвольные байты достаточны для общего Keil-контракта файловой целостности.
image.write_bytes(b"firmware bytes") # Проверяет сохранение точных байтов при нескольких копированиях.
with patch.object(Path, "unlink", unlink37), patch.object(shutil, "copytree", copytree37): # Воспроизводит оба ограничения Python 3.7 одновременно.
for attempt in range(2): # Второй экспорт должен объединять уже существующие каталоги.
result = identity.archive_build(image, generated / "firmware_identity.json", self.root / "archive", export=self.root / "export") # Печать папки соответствует настоящему postcompile Keil.
folder = Path(result["directory"]) # Использует точный архив текущего вызова.
(folder / "nested").mkdir(exist_ok=True) # Вложенный сопроводительный файл проверяет рекурсивное копирование.
(folder / "nested/map.txt").write_text("map " + str(attempt), encoding="utf-8") # Повторный выпуск обновляет существующий файл.
identity.copy_tree(folder, self.root / "ccs-output", copy_function=c2000.atomic_copy) # C2000 сохраняет атомарную замену с прежним интерфейсом unlink.
self.assertEqual((self.root / "ccs-output/nested/map.txt").read_text(encoding="utf-8"), "map 1") # Вложенные файлы обновились при повторном выпуске.
self.assertEqual((Path(result["exported_directory"]) / image.name).read_bytes(), image.read_bytes()) # Экспорт содержит исходный образ без изменения байтов.
self.assertFalse(list((self.root / "ccs-output").rglob(".firmware-*"))) # Успешная атомарная замена не оставляет незавершённых файлов.
if __name__ == "__main__": # Позволяет запустить регрессию стандартным unittest на компьютере сборки.
unittest.main() # Ненулевой результат выявляет несовместимость инструментов до настройки IDE.