Files
templates/tools/dsview/tests/check_decoder_scope_native.py
T

45 lines
8.2 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.
"""Проверка отдельного окна выбранного графика в настоящем встроенном DSView.""" # Сценарий использует готовую цифровую запись и не подключается к аппаратуре.
import argparse # Проверяемая сборка, запись и каталог результатов задаются явно.
import os # Изолированное окружение предотвращает смешение Qt6 родителя и Qt5 дочернего DSView.
from pathlib import Path # Абсолютные пути не зависят от рабочего каталога проверяемого компонента.
import subprocess # Сценарий управляет только собственным новым процессом DSView.
import time # Монотонные часы ограничивают ожидание нативной проверки и штатного завершения.
def main(): # Нативные действия выполняются встроенным тестовым обработчиком окна DSView.
parser = argparse.ArgumentParser(description=__doc__) # Описание доступно без создания окон через стандартный --help.
parser.add_argument('--runtime', type=Path, required=True) # Каталог должен содержать собранный DSView с decoder-scope.patch.
parser.add_argument('--capture', type=Path, required=True) # Сохранённая DSL-сессия содержит огибающую, коммутации и последний декодер pwm_motor.
parser.add_argument('--output', type=Path, required=True) # Отчёт, журнал и снимок выбранного графика помещаются отдельно от исходных данных.
parser.add_argument('--timeout', type=float, default=45) # Общий предел 45 секунд покрывает загрузку runtime, расчёт и пользовательские взаимодействия.
args = parser.parse_args() # Все обязательные параметры проверяются до запуска приложения.
from PySide6.QtWidgets import QApplication, QWidget # Родительское окно воспроизводит встраивание DSView в SETGUI через настоящий HWND.
runtime, capture, output = args.runtime.resolve(), args.capture.resolve(), args.output.resolve() # Проверка работает и при путях с пробелами без построения shell-команд.
output.mkdir(parents=True, exist_ok=True) # Каталог результатов создаётся в заданном пользователем месте.
report = output / 'decoder-scope.txt'; report.write_text('', encoding='utf-8') # Старый PASS не может скрыть провал нового запуска.
app = QApplication.instance() or QApplication([]); window = QWidget(); window.resize(1500, 1000); window.show() # Высота позволяет выбрать нижний график после цифровых входов NPC.
environment = {key: value for key, value in os.environ.items() if not key.startswith(('PYTHON', 'QT_', 'QML'))} # Нативный компонент загружает только собственные Python и плагины Qt.
environment.update(PATH=str(runtime) + os.pathsep + str(Path(os.environ['SystemRoot']) / 'System32'), DSVIEW_HOST_CHECK=str(report), DSVIEW_HOST_CHECK_SCOPE='1', DSVIEW_HOST_CHECK_DECODER_SCOPE='1') # Явные параметры включают только сценарий выбранного декодерного осциллографа.
with (output / 'decoder-scope.log').open('w', encoding='utf-8') as log: # Ошибки импорта и фактические размеры декодирования сохраняются для диагностики.
process = subprocess.Popen([str(runtime / 'DSView.exe'), '--loglevel', '3', '--embed-parent', str(int(window.winId())), str(capture)], cwd=runtime, env=environment, stdin=subprocess.PIPE, stdout=log, stderr=log, creationflags=subprocess.CREATE_NO_WINDOW) # Отдельный тестовый процесс не изменяет открытую рабочую сессию пользователя.
try: # Все пути завершения освобождают именно созданный этой проверкой процесс.
deadline = time.monotonic() + args.timeout # Ограниченное ожидание предотвращает бесконечную проверку при ошибке создания окна.
while process.poll() is None and not report.read_text(encoding='utf-8') and time.monotonic() < deadline: # Ожидается полный отчёт нативного взаимодействия, а не только открытие файла.
app.processEvents(); time.sleep(0.02) # Короткая пауза сохраняет обработку встраивания и штатных сообщений Qt.
result = report.read_text(encoding='utf-8') # Завершённый отчёт содержит результат выбора, масштаба, прокрутки, повторного открытия и удаления источника.
assert result.startswith('PASS '), result or 'Native scope check timed out' # Тайм-аут и частичный успех не признаются выполнением пользовательского сценария.
assert Path(str(report) + '.png').is_file(), 'No separate scope screenshot' # Проверка обязана сохранить настоящий снимок отдельной осциллограммы.
process.stdin.write(b'CLOSE\n'); process.stdin.flush(); deadline = time.monotonic() + 5 # Штатная команда закрывает только тестовый DSView после завершённых взаимодействий.
while process.poll() is None and time.monotonic() < deadline: # Родитель продолжает обрабатывать закрытие встроенного окна.
app.processEvents(); time.sleep(0.02) # Неблокирующий короткий опрос ограничивает задержку завершения.
assert process.poll() == 0, 'DSView did not close cleanly' # Ненулевой код выявляет повреждение жизненного цикла отдельного или основного окна.
text = (output / 'decoder-scope.log').read_text(encoding='utf-8', errors='replace') # Проверяется завершённый журнал после штатного выхода дочернего процесса.
assert 'Traceback (most recent call last)' not in text and text.count('Decoded sample count:120000') == 3, 'Expected one complete decode of each saved analyzer' # Три анализатора обрабатывают все отсчёты ровно один раз, включая все взаимодействия отдельного окна.
print(result.strip()); print(Path(str(report) + '.png')) # Краткий итог и путь к снимку позволяют быстро проверить результат.
finally: # Неудачная проверка не оставляет собственные окна DSView в фоне.
if process.poll() is None: process.terminate() # Чужие процессы и рабочие захваты пользователя не затрагиваются.
process.wait(timeout=5); window.close() # Освобождение системных дескрипторов завершается за ограниченное время.
if __name__ == '__main__': main() # Прямой запуск выполняет полный пользовательский сценарий в нативном приложении.