48 lines
8.1 KiB
Python
48 lines
8.1 KiB
Python
"""Проверка меню осциллографа в настоящем встроенном DSView с цифровой записью.""" # Запускает только собственный дочерний процесс и сохраняет изображение результата.
|
|
import argparse # Пути к проверяемому runtime и артефактам задаются явно.
|
|
import os # Изолированное окружение исключает смешение Qt5 дочернего процесса и Qt6 проверочного окна.
|
|
from pathlib import Path # Все артефакты размещаются в указанном каталоге.
|
|
import subprocess # Нативный DSView проходит тот же протокол встраивания, что в SETGUI.
|
|
import time # Монотонные часы ограничивают ожидание готовности и штатного завершения.
|
|
from check_pwm_native import fixture # Общая цифровая запись также проверяет сохранность готового декодера PWM.
|
|
def main(): # Проверка не подключается к USB или сети и не изменяет записи пользователя.
|
|
parser = argparse.ArgumentParser(description=__doc__) # Интерфейс сценария повторяет существующие нативные проверки рецепта.
|
|
parser.add_argument('--runtime', type=Path, required=True) # Проверяется конкретная собранная папка, а не установленный глобально DSView.
|
|
parser.add_argument('--output', type=Path, required=True) # Изображение, журнал и отчёт сохраняются вместе для диагностики.
|
|
args = parser.parse_args() # Некорректные аргументы завершают сценарий до создания GUI.
|
|
from PySide6.QtWidgets import QApplication, QWidget # Qt6 предоставляет настоящее родительское окно для нативного Qt5 DSView.
|
|
runtime, output = args.runtime.resolve(), args.output.resolve() # Дочерний процесс получает однозначные абсолютные пути.
|
|
output.mkdir(parents=True, exist_ok=True) # Каталог артефактов создаётся до подготовки проверочной записи.
|
|
capture, report, screenshot = output / 'digital.dsl', output / 'scope-check.txt', output / 'scope-native.png' # Каждый результат имеет отдельное назначение.
|
|
fixture(capture, seconds=0.008192) # Цифровая запись 8,192 мс содержит несколько периодов синуса и меандра эмулятора.
|
|
report.write_text('', encoding='utf-8') # Старый успешный отчёт не может скрыть ошибку нового запуска.
|
|
app = QApplication.instance() or QApplication([]) # Один цикл событий обслуживает родительскую область встраивания.
|
|
window = QWidget() # Изолированное окно принадлежит только проверке.
|
|
window.resize(1400, 900) # Размер в логических пикселях оставляет место для аналоговых, цифровых и декодированных дорожек.
|
|
window.show() # Настоящий HWND необходим протоколу --embed-parent DSView.
|
|
environment = {key: value for key, value in os.environ.items() if not key.startswith(('PYTHON', 'QT_', 'QML'))} # Дочерний Qt5 не наследует плагины и Python runtime Qt6.
|
|
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_SCREENSHOT=str(screenshot)) # Проверка меню и снимок включаются только для этого процесса.
|
|
with (output / 'scope-native.log').open('w', encoding='utf-8') as log: # Журнал сохраняется даже при нативном падении.
|
|
process = subprocess.Popen([str(runtime / 'DSView.exe'), '--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() + 30 # Тридцать секунд ограничивают запуск и проверку короткой записи.
|
|
while process.poll() is None and not report.read_text(encoding='utf-8') and time.monotonic() < deadline: # Ожидаем результата реальных действий меню, поддерживая родительский цикл событий.
|
|
app.processEvents() # Вложенный DSView получает resize и paint без блокировки окна.
|
|
time.sleep(0.02) # Короткая пауза ограничивает загрузку процессора при ожидании отчёта.
|
|
result = report.read_text(encoding='utf-8') # Отчёт формируется нативной проверкой добавления и удаления каналов.
|
|
assert result.startswith('PASS'), result or 'DSView не завершил проверку; см. scope-native.log' # Тайм-аут и аварийное завершение не выдаются за успех.
|
|
assert screenshot.is_file(), 'Не создан снимок осциллограммы' # Успех требует фактического рендера новых дорожек.
|
|
process.stdin.write(b'CLOSE\n') # Закрываем собственный экземпляр тем же способом, что SETGUI.
|
|
process.stdin.flush() # Команда немедленно поступает в канал нативного компонента.
|
|
deadline = time.monotonic() + 5 # Штатное завершение короткой синтетической записи не должно зависать.
|
|
while process.poll() is None and time.monotonic() < deadline: # Продолжаем обслуживать родителя до завершения дочернего процесса.
|
|
app.processEvents() # Встраивание остаётся действительным до закрытия DSView.
|
|
time.sleep(0.02) # Не выполняем активное ожидание завершения.
|
|
assert process.poll() == 0, 'DSView не завершился штатно' # Нативное падение после проверки также считается ошибкой.
|
|
print(result.strip(), screenshot) # Краткий отчёт указывает доступный для визуальной проверки артефакт.
|
|
finally: # Не оставляем проверочный DSView после провала проверки.
|
|
if process.poll() is None: process.terminate() # Завершаем только собственный дочерний процесс, не затрагивая пользовательские сессии.
|
|
process.wait(timeout=5) # Освобождаем дескрипторы процесса перед закрытием родительского окна.
|
|
window.close() # Проверочная область встраивания больше не нужна.
|
|
if __name__ == '__main__': main() # Прямой запуск сценария выполняет полный нативный smoke-тест.
|