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