#!/usr/bin/env python3 """Живой DAP-проход: C-макрос → breakpoint → обе console без DSS-вывода.""" from __future__ import annotations import json import os from pathlib import Path import subprocess import sys import tempfile import time ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(Path(__file__).resolve().parent)) sys.path.insert(0, str(ROOT/'toolchain')) from run_vscode_dap_probe import receive, send, wait_response from sdbg.server import rpc_call def main(): provider = 'osx' if '--osx' in sys.argv else 'sdbg' source = ROOT/'tests/sdbg/fixtures/logmacro.c' with tempfile.TemporaryDirectory(prefix='sprinter-macro-live-') as temp: exe = Path(temp)/'logmacro.exe' build = subprocess.run([str(ROOT/'bin/sprinter-cc'), '-o', str(exe), '--src-debug', str(source)], cwd=ROOT, env=dict(os.environ, SPRINTER_PYTHON=sys.executable), capture_output=True, text=True) if build.returncode: raise RuntimeError('Не собрано приложение с SDBG_LOG: '+build.stdout+build.stderr) process = subprocess.Popen([sys.executable, str(ROOT/'toolchain/sdbg_dap.py')], cwd=ROOT, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) buffer = b'' try: send(process, 1, 'initialize') _, buffer, _ = wait_response(process, buffer, 'initialize', 10) send(process, 2, 'launch', {'build': str(exe.with_name('.sprinter-cc-logmacro')), 'debugger': provider, 'dssTimeout': 30}) launched, buffer, events = wait_response(process, buffer, 'launch', 80) if not any(event.get('event') == 'initialized' for event in events): event, buffer = receive(process, buffer, 5) if event.get('event') != 'initialized': raise RuntimeError('Нет DAP initialized: '+str(event)) send(process, 3, 'configurationDone') _, buffer, _ = wait_response(process, buffer, 'configurationDone', 10) send(process, 4, 'stackTrace') frames, buffer, _ = wait_response(process, buffer, 'stackTrace', 10) if not frames['body']['stackFrames'][0]['name'].startswith('main'): raise RuntimeError('Перед стартом нет main: '+str(frames['body']['stackFrames'])) send(process, 5, 'continue') _, buffer, events = wait_response(process, buffer, 'continue', 10) outputs = [event for event in events if event.get('event') == 'output'] deadline = time.monotonic()+25 while not any('total=1\n' == event.get('body', {}).get('output') for event in outputs): if time.monotonic() >= deadline: raise RuntimeError('SDBG_LOG не пришёл в DAP Debug Console: '+str(outputs[-4:])) event, buffer = receive(process, buffer, deadline-time.monotonic()) if event.get('event') == 'terminated': raise RuntimeError('DAP завершился до авторского лога') if event.get('event') == 'output': outputs.append(event) socket = launched['body']['socket'] tail = rpc_call(socket, 'mame_console_tail', {'count': 50}, timeout=6) if not any('total=1' in line for line in tail['lines']): raise RuntimeError('В debugger console MAME нет total=1: '+str(tail)) print(json.dumps({'event': 'macro_dual_console_verified', 'provider': provider, 'dap_output': 'total=1', 'mame_console': [line for line in tail['lines'] if 'total=1' in line], 'entry': launched['body']['entry']}, ensure_ascii=False), flush=True) send(process, 6, 'disconnect') wait_response(process, buffer, 'disconnect', 10) finally: if process.poll() is None: process.terminate() try: _, stderr = process.communicate(timeout=8) except subprocess.TimeoutExpired: process.kill() _, stderr = process.communicate() if stderr: print(stderr.decode(errors='replace')[-3000:], file=sys.stderr) if __name__ == '__main__': try: main() except (RuntimeError, TimeoutError, OSError, KeyError, ValueError) as error: print('macro-log-probe: '+str(error), file=sys.stderr) sys.exit(1)