Расширить DSView осциллографом и декодерами двигателя и сигма-дельта

This commit is contained in:
2026-11-12 15:00:41 +03:00
parent fa5ea166a9
commit 1fc88684d5
41 changed files with 4147 additions and 151 deletions
@@ -0,0 +1,44 @@
"""Проверка отдельного окна выбранного графика в настоящем встроенном 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() # Прямой запуск выполняет полный пользовательский сценарий в нативном приложении.