Завершать DAP при потере остановленного MAME
This commit is contained in:
@@ -10,6 +10,7 @@ import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import select
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
@@ -90,6 +91,8 @@ def main():
|
||||
parser.add_argument('--build', default=None)
|
||||
parser.add_argument('--app-hdd', default=None)
|
||||
parser.add_argument('--launch-path', default=None)
|
||||
parser.add_argument('--exit-while-stopped', action='store_true',
|
||||
help='аварийно завершить собственный MAME на main и ждать DAP terminated')
|
||||
options, _ = parser.parse_known_args()
|
||||
if not PYTHON.is_file():
|
||||
raise RuntimeError('Нет local pyenv shim: '+str(PYTHON))
|
||||
@@ -121,6 +124,18 @@ def main():
|
||||
frame = frame_response['body']['stackFrames'][0]
|
||||
if frame['name'] != 'main':
|
||||
raise RuntimeError('Не main: '+str(frame))
|
||||
if options.exit_while_stopped:
|
||||
os.kill(launched['body']['mamePid'], signal.SIGKILL)
|
||||
deadline = time.monotonic()+15
|
||||
while time.monotonic() < deadline:
|
||||
message, buffer = receive(process, buffer, deadline-time.monotonic())
|
||||
if message.get('event') == 'terminated':
|
||||
print(json.dumps({'event': 'idle_mame_exit_terminates_dap',
|
||||
'frame': frame}, ensure_ascii=False), flush=True)
|
||||
send(process, 5, 'disconnect')
|
||||
wait_response(process, buffer, 'disconnect', 10)
|
||||
return 0
|
||||
raise RuntimeError('После закрытия остановленного MAME нет DAP terminated')
|
||||
if '--waitkey' in sys.argv:
|
||||
send(process, 5, 'setBreakpoints', {
|
||||
'source': {'path': str(ROOT/'tests/hello/hello.c')},
|
||||
|
||||
@@ -106,6 +106,14 @@ class DapTests(unittest.TestCase):
|
||||
self.assertIn('пропущено 2', events[0][1]['output'])
|
||||
self.assertEqual(events[1][1]['output'], 'total=42\n')
|
||||
|
||||
def test_closed_session_always_terminates_dap(self):
|
||||
self.dap._rpc_override = lambda method, args: {
|
||||
'events': [], 'first': 1, 'last': 0, 'closed': True,
|
||||
}
|
||||
events, closed = self.dap.poll_events(timeout=0)
|
||||
self.assertTrue(closed)
|
||||
self.assertEqual(events, [('terminated', {'restart': False})])
|
||||
|
||||
def test_stdio_framing_without_attach(self):
|
||||
requests = [
|
||||
{'seq': 1, 'type': 'request', 'command': 'initialize', 'arguments': {}},
|
||||
|
||||
@@ -18,10 +18,13 @@ class DummyModel:
|
||||
|
||||
|
||||
class DummyBridge:
|
||||
def __init__(self): self.calls = []
|
||||
def __init__(self):
|
||||
self.calls = []
|
||||
self.state = 'stopped'
|
||||
def close(self): pass
|
||||
def request(self, command, **arguments):
|
||||
self.calls.append((command, arguments))
|
||||
if command == 'snapshot': return {'state': self.state}
|
||||
if command == 'console_print': return {'printed': True}
|
||||
raise AssertionError(command)
|
||||
|
||||
@@ -37,6 +40,10 @@ class DummySession:
|
||||
def attach(self):
|
||||
return {'build_id': 'test', 'location': {'status': 'mapped'}}
|
||||
|
||||
def where(self, snapshot=None):
|
||||
return {'status': 'mapped', 'pc': 0x8100, 'link_address': 0x8100,
|
||||
'sources': [{'file': '/src/main.c', 'line': 3}]}
|
||||
|
||||
def break_line(self, filename, line, enabled=True):
|
||||
if line == 99:
|
||||
raise SessionError('нет адреса')
|
||||
@@ -131,6 +138,7 @@ class WaitingBridge:
|
||||
"""Машинный over ожидает внешний ввод, но pause должен остаться доступен."""
|
||||
def __init__(self):
|
||||
self.paused = False
|
||||
self.started = False
|
||||
self.calls = []
|
||||
|
||||
def close(self): pass
|
||||
@@ -138,11 +146,12 @@ class WaitingBridge:
|
||||
def request(self, command, **arguments):
|
||||
self.calls.append(command)
|
||||
if command == 'snapshot':
|
||||
return {'state': 'stopped' if self.paused else 'running'}
|
||||
return {'state': 'running' if self.started and not self.paused else 'stopped'}
|
||||
if command == 'pause':
|
||||
self.paused = True
|
||||
return {'accepted': True}
|
||||
if command == 'step_over':
|
||||
self.started = True
|
||||
return {'accepted': True}
|
||||
raise AssertionError(command)
|
||||
|
||||
@@ -180,6 +189,37 @@ class ServerTests(unittest.TestCase):
|
||||
server.close()
|
||||
controller.close()
|
||||
|
||||
def test_idle_snapshot_detects_invalidation(self):
|
||||
session = DummySession()
|
||||
controller = SessionController(session)
|
||||
try:
|
||||
session.bridge.state = 'invalidated'
|
||||
with controller.changed:
|
||||
self.assertTrue(controller.changed.wait_for(lambda: controller.closed,
|
||||
timeout=2))
|
||||
self.assertEqual(controller.events[-1]['event'], 'invalidated')
|
||||
self.assertEqual(controller.events[-1]['body']['reason'], 'reset_or_load')
|
||||
finally:
|
||||
controller.close()
|
||||
|
||||
def test_native_debugger_continue_is_reported(self):
|
||||
session = DummySession()
|
||||
controller = SessionController(session)
|
||||
try:
|
||||
session.bridge.state = 'running'
|
||||
with controller.changed:
|
||||
self.assertTrue(controller.changed.wait_for(lambda: controller.running,
|
||||
timeout=2))
|
||||
self.assertEqual(controller.events[-1]['event'], 'continued')
|
||||
self.assertEqual(controller.events[-1]['body']['reason'], 'external')
|
||||
session.bridge.state = 'stopped'
|
||||
with controller.changed:
|
||||
self.assertTrue(controller.changed.wait_for(lambda: not controller.running,
|
||||
timeout=2))
|
||||
self.assertEqual(controller.events[-1]['event'], 'stopped')
|
||||
finally:
|
||||
controller.close()
|
||||
|
||||
def test_source_breakpoints_replace_and_rollback(self):
|
||||
session = DummySession()
|
||||
controller = SessionController(session)
|
||||
|
||||
Reference in New Issue
Block a user