diff --git a/tests/helpers/debugsession.py b/tests/helpers/debugsession.py new file mode 100644 index 00000000..7a36df32 --- /dev/null +++ b/tests/helpers/debugsession.py @@ -0,0 +1,211 @@ +from __future__ import absolute_import + +import contextlib +import json +import sys +import time +import threading +import warnings + +from . import Closeable +from .message import ( + raw_read_all as read_messages, + raw_write_one as write_message +) +from .socket import create_client, close, recv_as_read, send_as_write +from .threading import get_locked_and_waiter +from .vsc import parse_message + + +class DebugSessionConnection(Closeable): + + VERBOSE = False + #VERBOSE = True + + TIMEOUT = 1.0 + + @classmethod + def create(cls, addr, timeout=TIMEOUT): + if timeout is None: + timeout = cls.TIMEOUT + sock = create_client() + for _ in range(int(timeout * 10)): + try: + sock.connect(addr) + except OSError: + if cls.VERBOSE: + print('+', end='') + sys.stdout.flush() + time.sleep(0.1) + else: + break + else: + raise RuntimeError('could not connect') + if cls.VERBOSE: + print('connected') + self = cls(sock, ownsock=True) + self._addr = addr + return self + + def __init__(self, sock, ownsock=False): + super(DebugSessionConnection, self).__init__() + self._sock = sock + self._ownsock = ownsock + + def iter_messages(self): + def stop(): + return self.closed + read = recv_as_read(self._sock) + for msg, _, _ in read_messages(read, stop=stop): + if self.VERBOSE: + print(msg) + yield parse_message(msg) + + def send(self, req): + def stop(): + return self.closed + write = send_as_write(self._sock) + body = json.dumps(req) + write_message(write, body, stop=stop) + + # internal methods + + def _close(self): + if self._ownsock: + close(self._sock) + + +class DebugSession(Closeable): + + VERBOSE = False + #VERBOSE = True + + @classmethod + def create(cls, addr=('localhost', 8888), **kwargs): + conn = DebugSessionConnection.create(addr) + return cls(conn, owned=True, **kwargs) + + def __init__(self, conn, seq=1000, handlers=(), timeout=None, owned=False): + super(DebugSession, self).__init__() + self._conn = conn + self._seq = seq + self._timeout = timeout + self._owned = owned + + self._handlers = [] + for handler in handlers: + if callable(handler): + self._add_handler(handler) + else: + self._add_handler(*handler) + self._received = [] + self._listenerthread = threading.Thread(target=self._listen) + self._listenerthread.start() + + @property + def received(self): + return list(self._received) + + def send_request(self, command, **args): + wait = args.pop('wait', True) + seq = self._seq + self._seq += 1 + req = { + 'type': 'request', + 'seq': seq, + 'command': command, + 'arguments': args, + } + if wait: + with self.wait_for_response(req): + self._conn.send(req) + else: + self._conn.send(req) + return req + + def add_handler(self, handler, **kwargs): + self._add_handler(handler, **kwargs) + + @contextlib.contextmanager + def wait_for_event(self, event, **kwargs): + def match(msg): + return msg.type == 'event' and msg.event == event + handlername = 'event {!r}'.format(event) + with self._wait_for_message(match, handlername, **kwargs): + yield + + @contextlib.contextmanager + def wait_for_response(self, req, **kwargs): + try: + command, seq = req.command, req.seq + except AttributeError: + command, seq = req['command'], req['seq'] + + def match(msg): + if msg.type != 'response': + return False + return msg.request_seq == seq + handlername = 'response (cmd:{} seq:{})'.format(command, seq) + with self._wait_for_message(match, handlername, **kwargs): + yield + + # internal methods + + def _close(self): + if self._owned: + self._conn.close() + if self._listenerthread != threading.current_thread(): + self._listenerthread.join(timeout=1.0) + if self._listenerthread.is_alive(): + warnings.warn('session listener still running') + self._check_handlers() + + def _listen(self): + try: + for msg in self._conn.iter_messages(): + if self.VERBOSE: + print(' ->', msg) + self._receive_message(msg) + except EOFError: + self.close() + + def _receive_message(self, msg): + for i, handler in enumerate(list(self._handlers)): + handle_message, _, _ = handler + handled = handle_message(msg) + try: + msg, handled = handled + except TypeError: + pass + if handled: + self._handlers.remove(handler) + break + self._received.append(msg) + + def _add_handler(self, handle_msg, handlername=None, required=True): + self._handlers.append( + (handle_msg, handlername, required)) + + def _check_handlers(self): + unhandled = [] + for handle_msg, name, required in self._handlers: + if not required: + continue + unhandled.append(name or repr(handle_msg)) + if unhandled: + raise RuntimeError('unhandled: {}'.format(unhandled)) + + @contextlib.contextmanager + def _wait_for_message(self, match, handlername, timeout=None): + lock, wait = get_locked_and_waiter() + + def handler(msg): + if not match(msg): + return msg, False + lock.release() + return msg, True + self._add_handler(handler, handlername) + try: + yield + finally: + wait(timeout or self._timeout, handlername) diff --git a/tests/helpers/editor.py b/tests/helpers/editor.py index 004ba18a..6ddb57a5 100644 --- a/tests/helpers/editor.py +++ b/tests/helpers/editor.py @@ -1,215 +1,8 @@ from __future__ import absolute_import -import contextlib -import json -import sys -import threading -import time -import warnings - from . import Closeable -from .message import ( - raw_read_all as read_messages, - raw_write_one as write_message -) +from .debugsession import DebugSession from .proc import Proc -from .socket import create_client, close, recv_as_read, send_as_write -from .threading import get_locked_and_waiter -from .vsc import parse_message - - -class DebugSessionConnection(Closeable): - - VERBOSE = False - #VERBOSE = True - - TIMEOUT = 1.0 - - @classmethod - def create(cls, addr, timeout=TIMEOUT): - if timeout is None: - timeout = cls.TIMEOUT - sock = create_client() - for _ in range(int(timeout * 10)): - try: - sock.connect(addr) - except OSError: - if cls.VERBOSE: - print('+', end='') - sys.stdout.flush() - time.sleep(0.1) - else: - break - else: - raise RuntimeError('could not connect') - if cls.VERBOSE: - print('connected') - self = cls(sock, ownsock=True) - self._addr = addr - return self - - def __init__(self, sock, ownsock=False): - super(DebugSessionConnection, self).__init__() - self._sock = sock - self._ownsock = ownsock - - def iter_messages(self): - def stop(): - return self.closed - read = recv_as_read(self._sock) - for msg, _, _ in read_messages(read, stop=stop): - if self.VERBOSE: - print(msg) - yield parse_message(msg) - - def send(self, req): - def stop(): - return self.closed - write = send_as_write(self._sock) - body = json.dumps(req) - write_message(write, body, stop=stop) - - # internal methods - - def _close(self): - if self._ownsock: - close(self._sock) - - -class DebugSession(Closeable): - - VERBOSE = False - #VERBOSE = True - - @classmethod - def create(cls, addr=('localhost', 8888), **kwargs): - conn = DebugSessionConnection.create(addr) - return cls(conn, owned=True, **kwargs) - - def __init__(self, conn, seq=1000, handlers=(), timeout=None, owned=False): - super(DebugSession, self).__init__() - self._conn = conn - self._seq = seq - self._timeout = timeout - self._owned = owned - - self._handlers = [] - for handler in handlers: - if callable(handler): - self._add_handler(handler) - else: - self._add_handler(*handler) - self._received = [] - self._listenerthread = threading.Thread(target=self._listen) - self._listenerthread.start() - - @property - def received(self): - return list(self._received) - - def send_request(self, command, **args): - wait = args.pop('wait', True) - seq = self._seq - self._seq += 1 - req = { - 'type': 'request', - 'seq': seq, - 'command': command, - 'arguments': args, - } - if wait: - with self.wait_for_response(req): - self._conn.send(req) - else: - self._conn.send(req) - return req - - def add_handler(self, handler, **kwargs): - self._add_handler(handler, **kwargs) - - @contextlib.contextmanager - def wait_for_event(self, event, **kwargs): - def match(msg): - return msg.type == 'event' and msg.event == event - handlername = 'event {!r}'.format(event) - with self._wait_for_message(match, handlername, **kwargs): - yield - - @contextlib.contextmanager - def wait_for_response(self, req, **kwargs): - try: - command, seq = req.command, req.seq - except AttributeError: - command, seq = req['command'], req['seq'] - - def match(msg): - if msg.type != 'response': - return False - return msg.request_seq == seq - handlername = 'response (cmd:{} seq:{})'.format(command, seq) - with self._wait_for_message(match, handlername, **kwargs): - yield - - # internal methods - - def _close(self): - if self._owned: - self._conn.close() - if self._listenerthread != threading.current_thread(): - self._listenerthread.join(timeout=1.0) - if self._listenerthread.is_alive(): - warnings.warn('session listener still running') - self._check_handlers() - - def _listen(self): - try: - for msg in self._conn.iter_messages(): - if self.VERBOSE: - print(' ->', msg) - self._receive_message(msg) - except EOFError: - self.close() - - def _receive_message(self, msg): - for i, handler in enumerate(list(self._handlers)): - handle_message, _, _ = handler - handled = handle_message(msg) - try: - msg, handled = handled - except TypeError: - pass - if handled: - self._handlers.remove(handler) - break - self._received.append(msg) - - def _add_handler(self, handle_msg, handlername=None, required=True): - self._handlers.append( - (handle_msg, handlername, required)) - - def _check_handlers(self): - unhandled = [] - for handle_msg, name, required in self._handlers: - if not required: - continue - unhandled.append(name or repr(handle_msg)) - if unhandled: - raise RuntimeError('unhandled: {}'.format(unhandled)) - - @contextlib.contextmanager - def _wait_for_message(self, match, handlername, timeout=None): - lock, wait = get_locked_and_waiter() - - def handler(msg): - if not match(msg): - return msg, False - lock.release() - return msg, True - self._add_handler(handler, handlername) - try: - yield - finally: - wait(timeout or self._timeout, handlername) class DebugAdapter(Closeable): diff --git a/tests/system_tests/test_main.py b/tests/system_tests/test_main.py index 138ad8dd..94b839c4 100644 --- a/tests/system_tests/test_main.py +++ b/tests/system_tests/test_main.py @@ -1,6 +1,7 @@ import unittest -from tests.helpers.editor import FakeEditor, get_locked_and_waiter +from tests.helpers.editor import FakeEditor +from tests.helpers.threading import get_locked_and_waiter from tests.helpers.vsc import parse_message from tests.helpers.workspace import Workspace, PathEntry