diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index be1ffdf7..0b26190a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,8 +34,8 @@ VSC Python settings for formating: ], ``` -### Running `pytest` based tests -We are currently migrating the tests to use `pytest`. Please run both set of tests. Newer tests must go into the [pytests](pytests) directory. Use [test_requirements.txt](test_requirements.txt) to install packages needed to run the tests. +### Running tests +We are currently migrating the tests to use `pytest`. Please run both set of tests. Newer tests must go into the [tests](tests) directory. Use [test_requirements.txt](test_requirements.txt) to install packages needed to run the tests. #### Windows ``` C:\> git clone https://github.com/Microsoft/ptvsd @@ -50,20 +50,7 @@ C:\ptvsd> py -3.7 -m pytest -v ~/ptvsd: python3 -m pip install -r ./test_requirements.txt ~/ptvsd: python3 -m pytest -v ``` -### Running `unittest` based tests -`git clone` ptvsd and change directory to `ptvsd`. Run the `tests` module from there. Newer tests must be written using `pytest` and must go into the [pytests](pytests) directory. Please do not add tests to this directory. -#### Windows -``` -C:\> git clone https://github.com/Microsoft/ptvsd -C:\> cd ptvsd -C:\ptvsd> py -3.7 -m tests -v -``` -#### Linux\Mac -``` -~: git clone https://github.com/Microsoft/ptvsd -~: cd ptvsd -~/ptvsd: python3 -m tests -v -``` + ### Debug in VSC using development version Set `PYTHONPATH` to point to cloned version of ptvsd, in `launch.json`, to debug any python project to test the debugger you are working on: diff --git a/README.md b/README.md index 94256377..004729b4 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ [![GitHub](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://raw.githubusercontent.com/Microsoft/ptvsd/master/LICENSE) [![PyPI](https://img.shields.io/pypi/v/ptvsd.svg)](https://pypi.org/project/ptvsd/) +This debugger is based on the Debug Adapter Protocol for VS Code: [debugProtocol.json](https://github.com/Microsoft/vscode-debugadapter-node/blob/master/debugProtocol.json) + ## `ptvsd` CLI Usage ### Debugging a script file To run a script file with debugging enabled, but without waiting for the debugger to attach (i.e. code starts executing immediately): diff --git a/debugger_protocol/README.md b/debugger_protocol/README.md deleted file mode 100644 index 06a032e4..00000000 --- a/debugger_protocol/README.md +++ /dev/null @@ -1,114 +0,0 @@ -# VSC Debugger Protocol - -[Visual Studio Code](https://code.visualstudio.com/) defines several -protocols that extensions may leverage to fully integrate with VSC -features. For ptvad the most notable of those is the debugger protocol. -When VSC handles debugger-related input via the UI, it delegates the -underlying behavior to an extension's debug adapter (e.g. ptvsd) via -the protocol. The -[debugger_protocol](https://github.com/ericsnowcurrently/ptvsd/blob/master/debugger_protocol) -package (at which you are looking) provides resources for understanding -and using the protocol. - -For more high-level info see: - -* [the VSC debug protocol page](https://code.visualstudio.com/docs/extensionAPI/api-debugging) -* [the example extension page](https://code.visualstudio.com/docs/extensions/example-debuggers) - - -## Protocol Definition - -The VSC debugger protocol has [a schema](https://github.com/Microsoft/vscode-debugadapter-node/blob/master/debugProtocol.json) -which defines its messages. The wire format is HTTP messages with JSON -bodies. Note that the schema does not define the semantics of the -protocol, though a large portion is elaborated in the "description" -fields in -[the schema document](https://github.com/Microsoft/vscode-debugadapter-node/blob/master/debugProtocol.json). - -[//]: # (TODO: Add a link to where the wire format is defined.) - - -## Components - -### Participants - -The VSC debugger protocol involves 2 participants: the `client` and the -`debug adapter`, AKA `server`. VSC is an example of a `client`. ptvsd -is an example of a `debug adapter`. VSC extensions are responsible for -providing the `debug adapter`, declaring it to VSC and connecting the -adapter to VSC when desired. - -### Communication - -Messages are sent back and forth over a socket. The messages are -JSON-encoded and sent as the body of an HTTP message. - -Flow: - - - -### Message Types - -All messages specify their `type` and a globally-unique -monotonically-increasing ID (`seq`). - -The protocol consists for 3 types of messages: - -* event -* request -* response - -An `event` is a message by which the `debug adapter` reports to the -`client` that something happened. Only the `debug adapter` sends -`event`s. An `event` may be sent at any time, so the `client` may get -one after sending a `request` but before receiving the corresponding -`response`. - -A `request` is a message by which the `client` requests something from -the `debug adapter` over the connection. That "something" may be data -corresponding to the state of the debugger or it may be an action that -should be performed. Note that the protocol dictates that the `debug -adapter` may also send `request`s to the `client`, but currently there -aren't any such `request` types. - -Each `request` type has a corresponding `response` type; and for each -`request` sent by the `client`, the `debug adapter` sends back the -corresponding `response`. `response` messages include a `request_seq` -field that matches the `seq` field of the corresponding `request`. - - -## Protocol-related Tools - -Tools related to the schema, as well as -[a vendored copy](https://github.com/ericsnowcurrently/ptvsd/blob/master/debugger_protocol/schema/debugProtocol.json) -of the schema file itself, are found in -[debugger_protocol/schema](https://github.com/Microsoft/ptvsd/tree/master/debugger_protocol/schema). -Python bindings for the messages are found in -[debugger_protocol/messages](https://github.com/ericsnowcurrently/ptvsd/blob/master/debugger_protocol/messages). -Tools for handling the wire format are found in -[debugger_protocol/messages/wireformat.py](https://github.com/ericsnowcurrently/ptvsd/blob/master/debugger_protocol/messages/wireformat.py). - -### Using the Python-implemented Message Types - -The Python implementations of the schema-defined messages all share a -[ProtocolMessage](https://github.com/ericsnowcurrently/ptvsd/blob/master/debugger_protocol/messages/message.py#L27) -base class. The 3 message types each have their own base class. Every -message class has the following methods to aid with serialization: - -* a `from_data(**raw)` factory method -* a `as_data()` method - -These methods are used by -[the wireformat helpers](https://github.com/ericsnowcurrently/ptvsd/blob/master/debugger_protocol/messages/wireformat.py). - - -## Other Resources - -* https://github.com/Microsoft/vscode-mock-debug -* https://github.com/Microsoft/vscode-debugadapter-node/tree/master/testSupport -* https://github.com/Microsoft/vscode-debugadapter-node/blob/master/protocol/src/debugProtocol.ts -* https://github.com/Microsoft/vscode-mono-debug - -* http://json-schema.org/latest/json-schema-core.html -* https://python-jsonschema.readthedocs.io/ -* http://python-jsonschema-objects.readthedocs.io/ diff --git a/debugger_protocol/_base.py b/debugger_protocol/_base.py deleted file mode 100644 index f0f8aff9..00000000 --- a/debugger_protocol/_base.py +++ /dev/null @@ -1,28 +0,0 @@ - - -class Readonly(object): - """For read-only instances.""" - - def __setattr__(self, name, value): - raise AttributeError( - '{} objects are read-only'.format(type(self).__name__)) - - def __delattr__(self, name): - raise AttributeError( - '{} objects are read-only'.format(type(self).__name__)) - - def _bind_attrs(self, **attrs): - for name, value in attrs.items(): - object.__setattr__(self, name, value) - - -class WithRepr(object): - - def _init_args(self): - # XXX Extract from __init__()... - return () - - def __repr__(self): - args = ', '.join('{}={!r}'.format(arg, value) - for arg, value in self._init_args()) - return '{}({})'.format(type(self).__name__, args) diff --git a/debugger_protocol/arg/__init__.py b/debugger_protocol/arg/__init__.py deleted file mode 100644 index ec58e37a..00000000 --- a/debugger_protocol/arg/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from ._common import NOT_SET, ANY # noqa -from ._datatype import FieldsNamespace # noqa -from ._decl import Enum, Union, Array, Mapping, Field # noqa -from ._errors import ( # noqa - ArgumentError, - ArgMissingError, IncompleteArgError, ArgTypeMismatchError, -) -from ._params import param_from_datatype # noqa diff --git a/debugger_protocol/arg/_common.py b/debugger_protocol/arg/_common.py deleted file mode 100644 index c36af8e7..00000000 --- a/debugger_protocol/arg/_common.py +++ /dev/null @@ -1,17 +0,0 @@ - -def sentinel(name): - """Return a named value to use as a sentinel.""" - class Sentinel(object): - def __repr__(self): - return name - - return Sentinel() - - -# NOT_SET indicates that an arg was not provided. -NOT_SET = sentinel('NOT_SET') - -# ANY is a datatype surrogate indicating that any value is okay. -ANY = sentinel('ANY') - -SIMPLE_TYPES = {None, bool, int, str} diff --git a/debugger_protocol/arg/_datatype.py b/debugger_protocol/arg/_datatype.py deleted file mode 100644 index b4d3000e..00000000 --- a/debugger_protocol/arg/_datatype.py +++ /dev/null @@ -1,307 +0,0 @@ -from debugger_protocol._base import Readonly, WithRepr -from ._common import NOT_SET, ANY, SIMPLE_TYPES -from ._decl import ( - _transform_datatype, _replace_ref, - Enum, Union, Array, Field, Fields) -from ._errors import ArgTypeMismatchError, ArgMissingError, IncompleteArgError - - -def _coerce(datatype, value, call=True): - if datatype is ANY: - return value - elif type(value) is datatype: - return value - elif value is datatype: - return value - elif datatype is None: - pass # fail below - elif datatype in SIMPLE_TYPES: - # We already checked for exact type match above. - pass # fail below - - # decl types - elif isinstance(datatype, Enum): - value = _coerce(datatype.datatype, value, call=False) - if value in datatype.choice: - return value - elif isinstance(datatype, Union): - for dt in datatype: - try: - return _coerce(dt, value, call=False) - except ArgTypeMismatchError: - continue - else: - raise ArgTypeMismatchError(value) - elif isinstance(datatype, Array): - try: - values = iter(value) - except TypeError: - raise ArgTypeMismatchError(value) - return [_coerce(datatype.itemtype, v, call=False) - for v in values] - elif isinstance(datatype, Field): - return _coerce(datatype.datatype, value) - elif isinstance(datatype, Fields): - class ArgNamespace(FieldsNamespace): - FIELDS = datatype - - return _coerce(ArgNamespace, value) - elif issubclass(datatype, FieldsNamespace): - arg = datatype.bind(value) - try: - arg_coerce = arg.coerce - except AttributeError: - return arg - else: - return arg_coerce() - - # fallbacks - elif callable(datatype) and call: - try: - return datatype(value) - except ArgTypeMismatchError: - raise - except (TypeError, ValueError): - raise ArgTypeMismatchError(value) - elif value == datatype: - return value - - raise ArgTypeMismatchError(value) - - -######################## -# fields - -class FieldsNamespace(Readonly, WithRepr): - """A namespace of field values exposed via attributes.""" - - FIELDS = None - PARAM_TYPE = None - PARAM = None - - _TRAVERSING = False - - @classmethod - def traverse(cls, op, **kwargs): - """Apply op to each field in cls.FIELDS.""" - if cls._TRAVERSING: # recursion check - return cls - cls._TRAVERSING = True - - fields = cls._normalize(cls.FIELDS) - try: - fields_traverse = fields.traverse - except AttributeError: - # must be normalizing right now... - return cls - fields = fields_traverse(op) - cls.FIELDS = cls._normalize(fields, force=True) - - cls._TRAVERSING = False - return cls - - @classmethod - def normalize(cls, *transforms): - """Normalize FIELDS and apply the given ops.""" - fields = cls._normalize(cls.FIELDS) - for transform in transforms: - fields = _transform_datatype(fields, transform) - fields = cls._normalize(fields) - cls.FIELDS = fields - return cls - - @classmethod - def _normalize(cls, fields, force=False): - if fields is None: - raise TypeError('missing FIELDS') - - try: - fixref = cls._fixref - except AttributeError: - fixref = cls._fixref = True - if not isinstance(fields, Fields): - fields = Fields(*fields) - if fixref or force: - cls._fixref = False - fields = _transform_datatype(fields, - lambda dt: _replace_ref(dt, cls)) - return fields - - @classmethod - def param(cls): - param = cls.PARAM - if param is None: - if cls.PARAM_TYPE is None: - return None - param = cls.PARAM_TYPE(cls.FIELDS, cls) - return param - - @classmethod - def bind(cls, ns, **kwargs): - if isinstance(ns, cls): - return ns - param = cls.param() - if param is None: - return cls(**ns) - return param.bind(ns, **kwargs) - - @classmethod - def _bind(cls, kwargs): - cls.FIELDS = cls._normalize(cls.FIELDS) - bound, missing = _fields_bind(cls.FIELDS, kwargs) - if missing: - raise IncompleteArgError(cls.FIELDS, missing) - - values = {} - validators = [] - serializers = {} - for field, arg in bound.items(): - if arg is NOT_SET: - continue - - try: - coerce = arg.coerce - except AttributeError: - value = arg - else: - value = coerce(arg) - values[field.name] = value - - try: - validate = arg.validate - validate = value.validate - except AttributeError: - pass - else: - validators.append(validate) - - try: - as_data = arg.as_data - as_data = value.as_data - except AttributeError: - pass - else: - serializers[field.name] = as_data - values['_validators'] = validators - values['_serializers'] = serializers - return values - - def __init__(self, **kwargs): - super(FieldsNamespace, self).__init__() - validate = kwargs.pop('_validate', True) - - kwargs = self._bind(kwargs) - self._bind_attrs(**kwargs) - if validate: - self.validate() - - def _init_args(self): - if self.FIELDS is not None: - for field in self.FIELDS: - try: - value = getattr(self, field.name) - except AttributeError: - continue - yield (field.name, value) - else: - for item in sorted(vars(self).items()): - yield item - - def __eq__(self, other): - try: - other_as_data = other.as_data - except AttributeError: - other_data = other - else: - other_data = other_as_data() - - return self.as_data() == other_data - - def __ne__(self, other): - return not (self == other) - - def validate(self): - """Ensure that the field values are valid.""" - for validate in self._validators: - validate() - - def as_data(self): - """Return serializable data for the instance.""" - data = {name: as_data() - for name, as_data in self._serializers.items()} - for field in self.FIELDS: - if field.name in data: - continue - try: - data[field.name] = getattr(self, field.name) - except AttributeError: - pass - return data - - -def _field_missing(field, value): - if value is NOT_SET: - return True - - try: - missing = field.datatype.missing - except AttributeError: - return None - else: - return missing(value) - - -def _field_bind(field, value, applydefaults=True): - missing = _field_missing(field, value) - if missing: - if field.optional: - if applydefaults: - return field.default - return NOT_SET - raise ArgMissingError(field, missing) - - try: - bind = field.datatype.bind - except AttributeError: - bind = (lambda v: _coerce(field.datatype, v)) - return bind(value) - - -def _fields_iter_values(fields, remainder): - for field in fields or (): - value = remainder.pop(field.name, NOT_SET) - yield field, value - - -def _fields_iter_bound(fields, remainder, applydefaults=True): - for field, value in _fields_iter_values(fields, remainder): - try: - arg = _field_bind(field, value, applydefaults=applydefaults) - except ArgMissingError as exc: - yield field, value, exc, False -# except ArgTypeMismatchError as exc: -# yield field, value, None, exc - else: - yield field, arg, False, False - - -def _fields_bind(fields, kwargs, applydefaults=True): - bound = {} - missing = {} - mismatched = {} - remainder = dict(kwargs) - bound_iter = _fields_iter_bound(fields, remainder, - applydefaults=applydefaults) - for field, arg, missed, mismatch in bound_iter: - if missed: - missing[field.name] = missed - elif mismatch: - mismatched[field.name] = arg - else: - bound[field] = arg - if remainder: - remainder = ', '.join(sorted(remainder)) - raise TypeError('got extra fields: {}'.format(remainder)) - if mismatched: - raise ArgTypeMismatchError(mismatched) - return bound, missing diff --git a/debugger_protocol/arg/_decl.py b/debugger_protocol/arg/_decl.py deleted file mode 100644 index cad37ba6..00000000 --- a/debugger_protocol/arg/_decl.py +++ /dev/null @@ -1,407 +0,0 @@ -from collections import namedtuple -try: - from collections.abc import Sequence -except ImportError: - from collections import Sequence - -from debugger_protocol._base import Readonly -from ._common import sentinel, NOT_SET, ANY, SIMPLE_TYPES - - -REF = '' -TYPE_REFERENCE = sentinel('TYPE_REFERENCE') - - -def _is_simple(datatype): - if datatype is ANY: - return True - elif datatype in list(SIMPLE_TYPES): - return True - elif isinstance(datatype, Enum): - return True - else: - return False - - -def _normalize_datatype(datatype): - cls = type(datatype) - # normalized when instantiated: - if isinstance(datatype, Union): - return datatype - elif isinstance(datatype, Array): - return datatype - elif isinstance(datatype, Array): - return datatype - elif isinstance(datatype, Mapping): - return datatype - elif isinstance(datatype, Fields): - return datatype - # do not need normalization: - elif datatype is TYPE_REFERENCE: - return TYPE_REFERENCE - elif datatype is ANY: - return ANY - elif datatype in list(SIMPLE_TYPES): - return datatype - elif isinstance(datatype, Enum): - return datatype - # convert to canonical types: - elif type(datatype) == type(REF) and datatype == REF: - return TYPE_REFERENCE - elif cls is set or cls is frozenset: - return Union.unordered(*datatype) - elif cls is list or cls is tuple: - datatype, = datatype - return Array(datatype) - elif cls is dict: - if len(datatype) != 1: - raise NotImplementedError - [keytype, valuetype], = datatype.items() - return Mapping(valuetype, keytype) - # fallback: - else: - try: - normalize = datatype.normalize - except AttributeError: - return datatype - else: - return normalize() - - -def _transform_datatype(datatype, op): - datatype = op(datatype) - try: - dt_traverse = datatype.traverse - except AttributeError: - pass - else: - datatype = dt_traverse(lambda dt: _transform_datatype(dt, op)) - return datatype - - -def _replace_ref(datatype, target): - if datatype is TYPE_REFERENCE: - return target - else: - return datatype - - -class Enum(namedtuple('Enum', 'datatype choice')): - """A simple type with a limited set of allowed values.""" - - @classmethod - def _check_choice(cls, datatype, choice, strict=True): - if callable(choice): - return choice - - if isinstance(choice, str): - msg = 'bad choice (expected {!r} values, got {!r})' - raise ValueError(msg.format(datatype, choice)) - - choice = frozenset(choice) - if not choice: - raise TypeError('missing choice') - if not strict: - return choice - - for value in choice: - if type(value) is not datatype: - msg = 'bad choice (expected {!r} values, got {!r})' - raise ValueError(msg.format(datatype, choice)) - return choice - - def __new__(cls, datatype, choice, **kwargs): - strict = kwargs.pop('strict', True) - normalize = kwargs.pop('_normalize', True) - (lambda: None)(**kwargs) # Make sure there aren't any other kwargs. - - if not isinstance(datatype, type): - raise ValueError('expected a class, got {!r}'.format(datatype)) - if datatype not in list(SIMPLE_TYPES): - msg = 'only simple datatypes are supported, got {!r}' - raise ValueError(msg.format(datatype)) - if normalize: - # There's no need to normalize datatype (it's a simple type). - pass - choice = cls._check_choice(datatype, choice, strict=strict) - - self = super(Enum, cls).__new__(cls, datatype, choice) - return self - - -class Union(tuple): - """Declare a union of different types. - - The declared order is preserved and respected. - - Sets and frozensets are treated Unions in declarations. - """ - - @classmethod - def unordered(cls, *datatypes, **kwargs): - """Return an unordered union of the given datatypes.""" - self = cls(*datatypes, **kwargs) - self._ordered = False - return self - - @classmethod - def _traverse(cls, datatypes, op): - changed = False - result = [] - for datatype in datatypes: - transformed = op(datatype) - if transformed is not datatype: - changed = True - result.append(transformed) - return result, changed - - def __new__(cls, *datatypes, **kwargs): - normalize = kwargs.pop('_normalize', True) - (lambda: None)(**kwargs) # Make sure there aren't any other kwargs. - - datatypes = list(datatypes) - if normalize: - datatypes, _ = cls._traverse( - datatypes, - lambda dt: _transform_datatype(dt, _normalize_datatype), - ) - self = super(Union, cls).__new__(cls, datatypes) - self._simple = all(_is_simple(dt) for dt in datatypes) - self._ordered = True - return self - - def __repr__(self): - return '{}{}'.format(type(self).__name__, tuple(self)) - - def __hash__(self): - return super(Union, self).__hash__() - - def __eq__(self, other): # honors order - if not isinstance(other, Union): - return NotImplemented - elif super(Union, self).__eq__(other): - return True - elif set(self) != set(other): - return False - elif self._simple and other._simple: - return True - elif not self._ordered and not other._ordered: - return True - else: - return NotImplemented - - def __ne__(self, other): - return not (self == other) - - @property - def datatypes(self): - return set(self) - - def traverse(self, op, **kwargs): - """Return a copy with op applied to each contained datatype.""" - datatypes, changed = self._traverse(self, op) - if not changed and not kwargs: - return self - updated = self.__class__(*datatypes, **kwargs) - if not self._ordered: - updated._ordered = False - return updated - - -class Array(Readonly): - """Declare an array (of a single type). - - Lists and tuples (single-item) are treated equivalently - in declarations. - """ - - def __init__(self, itemtype, _normalize=True): - if _normalize: - itemtype = _transform_datatype(itemtype, _normalize_datatype) - self._bind_attrs( - itemtype=itemtype, - ) - - def __repr__(self): - return '{}(itemtype={!r})'.format(type(self).__name__, self.itemtype) - - def __hash__(self): - return hash(self.itemtype) - - def __eq__(self, other): - try: - other_itemtype = other.itemtype - except AttributeError: - return False - return self.itemtype == other_itemtype - - def __ne__(self, other): - return not (self == other) - - def traverse(self, op, **kwargs): - """Return a copy with op applied to the item datatype.""" - datatype = op(self.itemtype) - if datatype is self.itemtype and not kwargs: - return self - return self.__class__(datatype, **kwargs) - - -class Mapping(Readonly): - """Declare a mapping (to a single type).""" - - def __init__(self, valuetype, keytype=str, _normalize=True): - if _normalize: - keytype = _transform_datatype(keytype, _normalize_datatype) - valuetype = _transform_datatype(valuetype, _normalize_datatype) - self._bind_attrs( - keytype=keytype, - valuetype=valuetype, - ) - - def __repr__(self): - if self.keytype is str: - return '{}(valuetype={!r})'.format( - type(self).__name__, self.valuetype) - else: - return '{}(keytype={!r}, valuetype={!r})'.format( - type(self).__name__, self.keytype, self.valuetype) - - def __hash__(self): - return hash((self.keytype, self.valuetype)) - - def __eq__(self, other): - try: - other_keytype = other.keytype - other_valuetype = other.valuetype - except AttributeError: - return False - if self.keytype != other_keytype: - return False - if self.valuetype != other_valuetype: - return False - return True - - def __ne__(self, other): - return not (self == other) - - def traverse(self, op, **kwargs): - """Return a copy with op applied to the item datatype.""" - keytype = op(self.keytype) - valuetype = op(self.valuetype) - if (keytype is self.keytype and - valuetype is self.valuetype and - not kwargs - ): - return self - return self.__class__(valuetype, keytype, **kwargs) - - -class Field(namedtuple('Field', 'name datatype default optional')): - """Declare a field in a data map param.""" - - START_OPTIONAL = sentinel('START_OPTIONAL') - - def __new__(cls, name, datatype=str, enum=None, default=NOT_SET, - optional=False, _normalize=True, **kwargs): - if enum is not None and not isinstance(enum, Enum): - datatype = Enum(datatype, enum) - enum = None - - if _normalize: - datatype = _transform_datatype(datatype, _normalize_datatype) - self = super(Field, cls).__new__( - cls, - name=str(name) if name else None, - datatype=datatype, - default=default, - optional=bool(optional), - ) - self._kwargs = kwargs.items() - return self - - @property - def kwargs(self): - return dict(self._kwargs) - - def traverse(self, op, **kwargs): - """Return a copy with op applied to the datatype.""" - datatype = op(self.datatype) - if datatype is self.datatype and not kwargs: - return self - kwargs.setdefault('default', self.default) - kwargs.setdefault('optional', self.optional) - return self.__class__(self.name, datatype, **kwargs) - - -class Fields(Readonly, Sequence): - """Declare a set of fields.""" - - @classmethod - def _iter_fixed(cls, fields, _normalize=True): - optional = None - for field in fields or (): - if field is Field.START_OPTIONAL: - if optional is not None: - raise RuntimeError('START_OPTIONAL used more than once') - optional = True - continue - - if not isinstance(field, Field): - raise TypeError('got non-field {!r}'.format(field)) - if _normalize: - field = _transform_datatype(field, _normalize_datatype) - if optional is not None and field.optional is not optional: - field = field._replace(optional=optional) - yield field - - def __init__(self, *fields, **kwargs): - fields = list(self._iter_fixed(fields, **kwargs)) - self._bind_attrs( - _fields=fields, - ) - - def __repr__(self): - return '{}(*{})'.format(type(self).__name__, self._fields) - - def __hash__(self): - return hash(tuple(self)) - - def __eq__(self, other): - try: - other_len = len(other) - other_iter = iter(other) - except TypeError: - return False - if len(self) != other_len: - return False - for i, item in enumerate(other_iter): - if self[i] != item: - return False - return True - - def __ne__(self, other): - return not (self == other) - - def __len__(self): - return len(self._fields) - - def __getitem__(self, index): - return self._fields[index] - - def as_dict(self): - return {field.name: field for field in self._fields} - - def traverse(self, op, **kwargs): - """Return a copy with op applied to each field.""" - changed = False - updated = [] - for field in self._fields: - transformed = op(field) - if transformed is not field: - changed = True - updated.append(transformed) - - if not changed and not kwargs: - return self - kwargs['_normalize'] = False - return self.__class__(*updated, **kwargs) diff --git a/debugger_protocol/arg/_errors.py b/debugger_protocol/arg/_errors.py deleted file mode 100644 index 03b600f5..00000000 --- a/debugger_protocol/arg/_errors.py +++ /dev/null @@ -1,32 +0,0 @@ - -class ArgumentError(TypeError): - """The base class for argument-related exceptions.""" - - -class ArgMissingError(ArgumentError): - """Indicates that the argument for the field is missing.""" - - def __init__(self, field): - super(ArgMissingError, self).__init__( - 'missing arg {!r}'.format(field.name)) - self.field = field - - -class IncompleteArgError(ArgumentError): - """Indicates that the "complex" arg has missing fields.""" - - def __init__(self, fields, missing): - msg = 'incomplete arg (missing or incomplete fields: {})' - super(IncompleteArgError, self).__init__( - msg.format(', '.join(sorted(missing)))) - self.fields = fields - self.missing = missing - - -class ArgTypeMismatchError(ArgumentError): - """Indicates that the arg did not have the expected type.""" - - def __init__(self, value): - super(ArgTypeMismatchError, self).__init__( - 'bad value {!r} (unsupported type)'.format(value)) - self.value = value diff --git a/debugger_protocol/arg/_param.py b/debugger_protocol/arg/_param.py deleted file mode 100644 index bc87c124..00000000 --- a/debugger_protocol/arg/_param.py +++ /dev/null @@ -1,221 +0,0 @@ -from debugger_protocol._base import Readonly, WithRepr - - -class _ParameterBase(WithRepr): - - def __init__(self, datatype): - self._datatype = datatype - - def _init_args(self): - yield ('datatype', self._datatype) - - def __hash__(self): - try: - return hash(self._datatype) - except TypeError: - return hash(id(self)) - - def __eq__(self, other): - if type(self) is not type(other): - return NotImplemented - return self._datatype == other._datatype - - def __ne__(self, other): - return not (self == other) - - @property - def datatype(self): - return self._datatype - - -class Parameter(_ParameterBase): - """Base class for different parameter types.""" - - def __init__(self, datatype, handler=None): - super(Parameter, self).__init__(datatype) - self._handler = handler - - def _init_args(self): - for item in super(Parameter, self)._init_args(): - yield item - if self._handler is not None: - yield ('handler', self._handler) - - def bind(self, raw): - """Return an Arg for the given raw value. - - As with match_type(), if the value is not supported by this - parameter return None. - """ - handler = self.match_type(raw) - if handler is None: - return None - return Arg(self, raw, handler) - - def match_type(self, raw): - """Return the datatype handler to use for the given raw value. - - If the value does not match then return None. - """ - return self._handler - - -class DatatypeHandler(_ParameterBase): - """Base class for datatype handlers.""" - - def coerce(self, raw): - """Return the deserialized equivalent of the given raw value.""" - # By default this is a noop. - return raw - - def validate(self, coerced): - """Ensure that the already-deserialized value is correct.""" - # By default this is a noop. - return - - def as_data(self, coerced): - """Return a serialized equivalent of the given value. - - This method round-trips with the "coerce()" method. - """ - # By default this is a noop. - return coerced - - -class Arg(Readonly, WithRepr): - """The bridge between a raw value and a deserialized one. - - This is primarily the product of Parameter.bind(). - """ - # The value of this type lies in encapsulating intermediate state - # and in caching data. - - def __init__(self, param, value, handler=None, israw=True): - if not isinstance(param, Parameter): - raise TypeError( - 'bad param (expected Parameter, got {!r})'.format(param)) - if handler is None: - if israw: - handler = param.match_type(value) - else: - raise TypeError('missing handler') - if not isinstance(handler, DatatypeHandler): - msg = 'bad handler (expected DatatypeHandler, got {!r})' - raise TypeError(msg.format(handler)) - - key = '_raw' if israw else '_value' - kwargs = {key: value} - self._bind_attrs( - param=param, - _handler=handler, - _validated=False, - **kwargs - ) - - def _init_args(self): - yield ('param', self.param) - israw = True - try: - yield ('value', self._raw) - except AttributeError: - yield ('value', self._value) - israw = False - if self.datatype != self.param.datatype: - yield ('handler', self._handler) - if not israw: - yield ('israw', False) - - def __hash__(self): - try: - return hash(self.datatype) - except TypeError: - return hash(id(self)) - - def __eq__(self, other): - if type(self) is not type(other): - return False - if self.param != other.param: - return False - return self._as_data() == other._as_data() - - def __ne__(self, other): - return not (self == other) - - @property - def datatype(self): - return self._handler.datatype - - @property - def raw(self): - """The serialized value.""" - return self.as_data() - - @property - def value(self): - """The de-serialized value.""" - value = self.coerce() - if not self._validated: - self._validate() - return value - - def coerce(self, cached=True): - """Return the deserialized equivalent of the raw value.""" - if not cached: - try: - raw = self._raw - except AttributeError: - # Use the cached value anyway. - return self._value - else: - return self._handler.coerce(raw) - - try: - return self._value - except AttributeError: - value = self._handler.coerce(self._raw) - self._bind_attrs( - _value=value, - ) - return value - - def validate(self, force=False): - """Ensure that the (deserialized) value is correct. - - If the value has a "validate()" method then it gets called. - Otherwise it's up to the handler. - """ - if not self._validated or force: - self.coerce() - self._validate() - - def _validate(self): - try: - validate = self._value.validate - except AttributeError: - self._handler.validate(self._value) - else: - validate() - self._bind_attrs( - _validated=True, - ) - - def as_data(self, cached=True): - """Return a serialized equivalent of the value.""" - self.validate() - if not cached: - return self._handler.as_data(self._value) - return self._as_data() - - def _as_data(self): - try: - return self._raw - except AttributeError: - try: - as_data = self._value.as_data - except AttributeError: - as_data = self._handler.as_data - raw = as_data(self._value) - self._bind_attrs( - _raw=raw, - ) - return raw diff --git a/debugger_protocol/arg/_params.py b/debugger_protocol/arg/_params.py deleted file mode 100644 index 5562d623..00000000 --- a/debugger_protocol/arg/_params.py +++ /dev/null @@ -1,476 +0,0 @@ -from ._common import NOT_SET, ANY, SIMPLE_TYPES -from ._datatype import FieldsNamespace -from ._decl import Enum, Union, Array, Mapping, Field, Fields -from ._errors import ArgTypeMismatchError -from ._param import Parameter, DatatypeHandler - - -#def as_parameter(cls): -# """Return a parameter that wraps the given FieldsNamespace subclass.""" -# # XXX inject_params -# cls.normalize(_inject_params) -# param = param_from_datatype(cls) -## cls.PARAM = param -# return param -# -# -#def _inject_params(datatype): -# return param_from_datatype(datatype) - - -def param_from_datatype(datatype, **kwargs): - """Return a parameter for the given datatype.""" - if isinstance(datatype, Parameter): - return datatype - - if isinstance(datatype, DatatypeHandler): - return Parameter(datatype.datatype, datatype, **kwargs) - elif isinstance(datatype, Fields): - return ComplexParameter(datatype, **kwargs) - elif isinstance(datatype, Field): - return param_from_datatype(datatype.datatype, **kwargs) - elif datatype is ANY: - return NoopParameter() - elif datatype is None: - return SingletonParameter(None) - elif datatype in list(SIMPLE_TYPES): - return SimpleParameter(datatype, **kwargs) - elif isinstance(datatype, Enum): - return EnumParameter(datatype.datatype, datatype.choice, **kwargs) - elif isinstance(datatype, Union): - return UnionParameter(datatype, **kwargs) - elif isinstance(datatype, (set, frozenset)): - return UnionParameter(Union(*datatype), **kwargs) - elif isinstance(datatype, Array): - return ArrayParameter(datatype, **kwargs) - elif isinstance(datatype, (list, tuple)): - datatype, = datatype - return ArrayParameter(Array(datatype), **kwargs) - elif not isinstance(datatype, type): - raise NotImplementedError - elif issubclass(datatype, FieldsNamespace): - param = datatype.param() - return param or ComplexParameter(datatype, **kwargs) - else: - raise NotImplementedError - - -######################## -# param types - -class NoopParameter(Parameter): - """A parameter that treats any value as-is.""" - def __init__(self): - handler = DatatypeHandler(ANY) - super(NoopParameter, self).__init__(ANY, handler) - - -NOOP = NoopParameter() - - -class SingletonParameter(Parameter): - """A parameter that works only for the given value.""" - - class HANDLER(DatatypeHandler): - def validate(self, coerced): - if coerced is not self.datatype: - raise ValueError( - 'expected {!r}, got {!r}'.format(self.datatype, coerced)) - - def __init__(self, obj): - handler = self.HANDLER(obj) - super(SingletonParameter, self).__init__(obj, handler) - - def match_type(self, raw): - # Note we do not check equality for singletons. - if raw is not self.datatype: - return None - return super(SingletonParameter, self).match_type(raw) - - -class SimpleHandler(DatatypeHandler): - """A datatype handler for basic value types.""" - - def __init__(self, cls): - if not isinstance(cls, type): - raise ValueError('expected a class, got {!r}'.format(cls)) - super(SimpleHandler, self).__init__(cls) - - def coerce(self, raw): - if type(raw) is self.datatype: - return raw - return self.datatype(raw) - - def validate(self, coerced): - if type(coerced) is not self.datatype: - raise ValueError( - 'expected {!r}, got {!r}'.format(self.datatype, coerced)) - - -class SimpleParameter(Parameter): - """A parameter for basic value types.""" - - HANDLER = SimpleHandler - - def __init__(self, cls, strict=True): - handler = self.HANDLER(cls) - super(SimpleParameter, self).__init__(cls, handler) - self._strict = strict - - def match_type(self, raw): - if self._strict: - if type(raw) is not self.datatype: - return None - elif not isinstance(raw, self.datatype): - return None - return super(SimpleParameter, self).match_type(raw) - - -class EnumParameter(Parameter): - """A parameter for enums of basic value types.""" - - class HANDLER(SimpleHandler): - - def __init__(self, cls, enum): - if not enum: - raise TypeError('missing enum') - super(EnumParameter.HANDLER, self).__init__(cls) - if not callable(enum): - enum = set(enum) - self.enum = enum - - def validate(self, coerced): - super(EnumParameter.HANDLER, self).validate(coerced) - - if not self._match_enum(coerced): - msg = 'expected one of {!r}, got {!r}' - raise ValueError(msg.format(self.enum, coerced)) - - def _match_enum(self, coerced): - if callable(self.enum): - if not self.enum(coerced): - return False - elif coerced not in self.enum: - return False - return True - - def __init__(self, cls, enum): - handler = self.HANDLER(cls, enum) - super(EnumParameter, self).__init__(cls, handler) - self._match_enum = handler._match_enum - - def match_type(self, raw): - if type(raw) is not self.datatype: - return None - if not self._match_enum(raw): - return None - return super(EnumParameter, self).match_type(raw) - - -class UnionParameter(Parameter): - """A parameter that supports multiple different types.""" - - HANDLER = None # no handler - - @classmethod - def from_datatypes(cls, *datatypes, **kwargs): - datatype = Union(*datatypes) - return cls(datatype, **kwargs) - - def __init__(self, datatype, **kwargs): - if not isinstance(datatype, Union): - raise ValueError('expected Union, got {!r}'.format(datatype)) - super(UnionParameter, self).__init__(datatype) - - choice = [] - for dt in datatype: - param = param_from_datatype(dt) - choice.append(param) - self.choice = choice - - def __eq__(self, other): - if type(self) is not type(other): - return False - return set(self.datatype) == set(other.datatype) - - def match_type(self, raw): - for param in self.choice: - handler = param.match_type(raw) - if handler is not None: - return handler - return None - - -class ArrayParameter(Parameter): - """A parameter that is a list of some fixed type.""" - - class HANDLER(DatatypeHandler): - - def __init__(self, datatype, handlers=None, itemparam=None): - if not isinstance(datatype, Array): - raise ValueError( - 'expected an Array, got {!r}'.format(datatype)) - super(ArrayParameter.HANDLER, self).__init__(datatype) - self.handlers = handlers - self.itemparam = itemparam - - def coerce(self, raw): - if self.handlers is None: - if self.itemparam is None: - itemtype = self.datatype.itemtype - self.itemparam = param_from_datatype(itemtype) - handlers = [] - for item in raw: - handler = self.itemparam.match_type(item) - if handler is None: - raise ArgTypeMismatchError(item) - handlers.append(handler) - self.handlers = handlers - - result = [] - for i, item in enumerate(raw): - handler = self.handlers[i] - item = handler.coerce(item) - result.append(item) - return result - - def validate(self, coerced): - if self.handlers is None: - raise TypeError('coerce first') - for i, item in enumerate(coerced): - handler = self.handlers[i] - handler.validate(item) - - def as_data(self, coerced): - if self.handlers is None: - raise TypeError('coerce first') - data = [] - for i, item in enumerate(coerced): - handler = self.handlers[i] - datum = handler.as_data(item) - data.append(datum) - return data - - @classmethod - def from_itemtype(cls, itemtype, **kwargs): - datatype = Array(itemtype) - return cls(datatype, **kwargs) - - def __init__(self, datatype): - if not isinstance(datatype, Array): - raise ValueError('expected Array, got {!r}'.format(datatype)) - itemparam = param_from_datatype(datatype.itemtype) - handler = self.HANDLER(datatype, None, itemparam) - super(ArrayParameter, self).__init__(datatype, handler) - - self.itemparam = itemparam - - def match_type(self, raw): - if not isinstance(raw, list): - return None - handlers = [] - for item in raw: - handler = self.itemparam.match_type(item) - if handler is None: - return None - handlers.append(handler) - return self.HANDLER(self.datatype, handlers) - - -class MappingParameter(Parameter): - """A parameter that is a mapping of some fixed type.""" - - class HANDLER(DatatypeHandler): - - def __init__(self, datatype, handlers=None, - keyparam=None, valueparam=None): - if not isinstance(datatype, Mapping): - raise ValueError( - 'expected an Mapping, got {!r}'.format(datatype)) - super(MappingParameter.HANDLER, self).__init__(datatype) - self.handlers = handlers - self.keyparam = keyparam - self.valueparam = valueparam - - def coerce(self, raw): - if self.handlers is None: - if self.keyparam is None: - keytype = self.datatype.keytype - self.keyparam = param_from_datatype(keytype) - if self.valueparam is None: - valuetype = self.datatype.valuetype - self.valueparam = param_from_datatype(valuetype) - handlers = {} - for key, value in raw.items(): - keyhandler = self.keyparam.match_type(key) - if keyhandler is None: - raise ArgTypeMismatchError(key) - valuehandler = self.valueparam.match_type(value) - if valuehandler is None: - raise ArgTypeMismatchError(value) - handlers[key] = (keyhandler, valuehandler) - self.handlers = handlers - - result = {} - for key, value in raw.items(): - keyhandler, valuehandler = self.handlers[key] - key = keyhandler.coerce(key) - value = valuehandler.coerce(value) - result[key] = value - return result - - def validate(self, coerced): - if self.handlers is None: - raise TypeError('coerce first') - for key, value in coerced.items(): - keyhandler, valuehandler = self.handlers[key] - keyhandler.validate(key) - valuehandler.validate(value) - - def as_data(self, coerced): - if self.handlers is None: - raise TypeError('coerce first') - data = {} - for key, value in coerced.items(): - keyhandler, valuehandler = self.handlers[key] - key = keyhandler.as_data(key) - value = valuehandler.as_data(value) - data[key] = value - return data - - @classmethod - def from_valuetype(cls, valuetype, keytype=str, **kwargs): - datatype = Mapping(valuetype, keytype) - return cls(datatype, **kwargs) - - def __init__(self, datatype): - if not isinstance(datatype, Mapping): - raise ValueError('expected Mapping, got {!r}'.format(datatype)) - keyparam = param_from_datatype(datatype.keytype) - valueparam = param_from_datatype(datatype.valuetype) - handler = self.HANDLER(datatype, None, keyparam, valueparam) - super(MappingParameter, self).__init__(datatype, handler) - - self.keyparam = keyparam - self.valueparam = valueparam - - def match_type(self, raw): - if not isinstance(raw, dict): - return None - handlers = {} - for key, value in raw.items(): - keyhandler = self.keyparam.match_type(key) - if keyhandler is None: - return None - valuehandler = self.valueparam.match_type(value) - if valuehandler is None: - return None - handlers[key] = (keyhandler, valuehandler) - return self.HANDLER(self.datatype, handlers) - - -class ComplexParameter(Parameter): - - class HANDLER(DatatypeHandler): - - def __init__(self, datatype, handlers=None): - if (type(datatype) is not type or - not issubclass(datatype, FieldsNamespace) - ): - msg = 'expected FieldsNamespace, got {!r}' - raise ValueError(msg.format(datatype)) - super(ComplexParameter.HANDLER, self).__init__(datatype) - self.handlers = handlers - - def coerce(self, raw): - if self.handlers is None: - fields = self.datatype.FIELDS.as_dict() - handlers = {} - for name, value in raw.items(): - param = param_from_datatype(fields[name]) - handler = param.match_type(value) - if handler is None: - raise ArgTypeMismatchError((name, value)) - handlers[name] = handler - self.handlers = handlers - - result = {} - for name, value in raw.items(): - handler = self.handlers[name] - value = handler.coerce(value) - result[name] = value - return self.datatype(**result) - - def validate(self, coerced): - if self.handlers is None: - raise TypeError('coerce first') - for field in self.datatype.FIELDS: - try: - value = getattr(coerced, field.name) - except AttributeError: - continue - handler = self.handlers[field.name] - handler.validate(value) - - def as_data(self, coerced): - if self.handlers is None: - raise TypeError('coerce first') - data = {} - for field in self.datatype.FIELDS: - try: - value = getattr(coerced, field.name) - except AttributeError: - continue - handler = self.handlers[field.name] - datum = handler.as_data(value) - data[field.name] = datum - return data - - def __init__(self, datatype): - if isinstance(datatype, Fields): - class ArgNamespace(FieldsNamespace): - FIELDS = datatype - - datatype = ArgNamespace - elif (type(datatype) is not type or - not issubclass(datatype, FieldsNamespace)): - msg = 'expected Fields or FieldsNamespace, got {!r}' - raise ValueError(msg.format(datatype)) - datatype.normalize() - datatype.PARAM = self - # We set handler later in match_type(). - super(ComplexParameter, self).__init__(datatype) - - self.params = {field.name: param_from_datatype(field) - for field in datatype.FIELDS} - - def __eq__(self, other): - if super(ComplexParameter, self).__eq__(other): - return True - try: - fields = self._datatype.FIELDS - other_fields = other._datatype.FIELDS - except AttributeError: - return NotImplemented - else: - return fields == other_fields - - def match_type(self, raw): - if not isinstance(raw, dict): - return None - handlers = {} - for field in self.datatype.FIELDS: - try: - value = raw[field.name] - except KeyError: - if not field.optional: - return None - value = field.default - if value is NOT_SET: - continue - param = self.params[field.name] - handler = param.match_type(value) - if handler is None: - return None - handlers[field.name] = handler - return self.HANDLER(self.datatype, handlers) diff --git a/debugger_protocol/messages/__init__.py b/debugger_protocol/messages/__init__.py deleted file mode 100644 index 6a171a16..00000000 --- a/debugger_protocol/messages/__init__.py +++ /dev/null @@ -1,76 +0,0 @@ - - -MESSAGE_TYPES = {} -MESSAGE_TYPE_KEYS = {} - - -def register(cls, msgtype=None, typekey=None, key=None): - """Add the message class to the registry. - - The class is also fixed up if necessary. - """ - if not isinstance(cls, type): - raise RuntimeError('may not be used as a decorator factory.') - - if msgtype is None: - if cls.TYPE is None: - raise RuntimeError('class missing TYPE') - msgtype = cls.TYPE - if typekey is None: - if cls.TYPE_KEY is None: - raise RuntimeError('class missing TYPE_KEY') - typekey = cls.TYPE_KEY - if key is None: - key = getattr(cls, typekey, - getattr(cls, typekey.upper(), None)) - if not key: - raise RuntimeError('missing type key attribute') - - try: - registered = MESSAGE_TYPES[msgtype] - except KeyError: - registered = MESSAGE_TYPES[msgtype] = {} - MESSAGE_TYPE_KEYS[msgtype] = typekey - else: - if typekey != MESSAGE_TYPE_KEYS[msgtype]: - msg = 'mismatch on TYPE_KEY ({!r} != {!r})' - raise RuntimeError( - msg.format(typekey, MESSAGE_TYPE_KEYS[msgtype])) - - if key in registered: - raise RuntimeError('{}:{} already registered'.format(msgtype, key)) - registered[key] = cls - - # XXX init args - - return cls - - -def look_up(data): - """Return the message class for the given raw message data.""" - msgtype = data['type'] - typekey = MESSAGE_TYPE_KEYS[msgtype] - key = data[typekey] - return MESSAGE_TYPES[msgtype][key] - - -class Message(object): - """The API for register-able message types.""" - - TYPE = None - TYPE_KEY = None - - @classmethod - def from_data(cls, **kwargs): - """Return an instance based on the given raw data.""" - raise NotImplementedError - - def as_data(self): - """Return serializable data for the instance.""" - raise NotImplementedError - - -# Force registration. -from .message import ProtocolMessage, Request, Response, Event # noqa -from .requests import * # noqa -from .events import * # noqa diff --git a/debugger_protocol/messages/_requests.py b/debugger_protocol/messages/_requests.py deleted file mode 100644 index 27e2279f..00000000 --- a/debugger_protocol/messages/_requests.py +++ /dev/null @@ -1,336 +0,0 @@ -from debugger_protocol.arg import FieldsNamespace, Field, Enum -from .shared import Checksum, Source - - -class Message(FieldsNamespace): - """A structured message object. - - Used to return errors from requests. - """ - FIELDS = [ - Field('id', int), - Field('format'), - Field.START_OPTIONAL, - Field('variables', {str: str}), - Field('sendTelemetry', bool), - Field('showUser', bool), - Field('url'), - Field('urlLabel'), - ] - - -class ExceptionBreakpointsFilter(FieldsNamespace): - """ - An ExceptionBreakpointsFilter is shown in the UI as an option for - configuring how exceptions are dealt with. - """ - - FIELDS = [ - Field('filter'), - Field('label'), - Field.START_OPTIONAL, - Field('default', bool), - ] - - -class ColumnDescriptor(FieldsNamespace): - """ - A ColumnDescriptor specifies what module attribute to show in a - column of the ModulesView, how to format it, and what the column's - label should be. It is only used if the underlying UI actually - supports this level of customization. - """ - - TYPES = {"string", "number", "boolean", "unixTimestampUTC"} - FIELDS = [ - Field('attributeName'), - Field('label'), - Field.START_OPTIONAL, - Field('format'), - Field('type'), - Field('width', int), - ] - - -class Capabilities(FieldsNamespace): - """Information about the capabilities of a debug adapter.""" - - FIELDS = [ - Field.START_OPTIONAL, - Field('supportsConfigurationDoneRequest', bool), - Field('supportsFunctionBreakpoints', bool), - Field('supportsConditionalBreakpoints', bool), - Field('supportsHitConditionalBreakpoints', bool), - Field('supportsEvaluateForHovers', bool), - Field('exceptionBreakpointFilters', [ExceptionBreakpointsFilter]), - Field('supportsStepBack', bool), - Field('supportsSetVariable', bool), - Field('supportsRestartFrame', bool), - Field('supportsGotoTargetsRequest', bool), - Field('supportsStepInTargetsRequest', bool), - Field('supportsCompletionsRequest', bool), - Field('supportsModulesRequest', bool), - Field('additionalModuleColumns', [ColumnDescriptor]), - Field('supportedChecksumAlgorithms', [Enum(str, Checksum.ALGORITHMS)]), - Field('supportsRestartRequest', bool), - Field('supportsExceptionOptions', bool), - Field('supportsValueFormattingOptions', bool), - Field('supportsExceptionInfoRequest', bool), - Field('supportsLogPoints', bool), - Field('supportTerminateDebuggee', bool), - Field('supportsDelayedStackTraceLoading', bool), - Field('supportsLoadedSourcesRequest', bool), - Field('supportsSetExpression', bool), - Field('supportsModulesRequest', bool), - Field('supportsDebuggerProperties', bool), - Field('supportsCompletionsRequest', bool), - ] - - -class ModulesViewDescriptor(FieldsNamespace): - """ - The ModulesViewDescriptor is the container for all declarative - configuration options of a ModuleView. For now it only specifies - the columns to be shown in the modules view. - """ - - FIELDS = [ - Field('columns', [ColumnDescriptor]), - ] - - -class Thread(FieldsNamespace): - """A thread.""" - - FIELDS = [ - Field('id', int), - Field('name'), - ] - - -class StackFrame(FieldsNamespace): - """A Stackframe contains the source location.""" - - PRESENTATION_HINTS = {"normal", "label", "subtle"} - FIELDS = [ - Field('id', int), - Field('name'), - Field('source', Source, optional=True), - Field('line', int), - Field('column', int), - Field.START_OPTIONAL, - Field('endLine', int), - Field('endColumn', int), - Field("moduleId", {int, str}), - Field('presentationHint'), - ] - - -class Scope(FieldsNamespace): - """ - A Scope is a named container for variables. Optionally a scope - can map to a source or a range within a source. - """ - - FIELDS = [ - Field('name'), - Field('variablesReference', int), - Field('namedVariables', int, optional=True), - Field('indexedVariables', int, optional=True), - Field('expensive', bool), - Field.START_OPTIONAL, - Field('source', Source), - Field('line', int), - Field('column', int), - Field('endLine', int), - Field('endColumn', int), - ] - - -class VariablePresentationHint(FieldsNamespace): - """ - Optional properties of a variable that can be used to determine - how to render the variable in the UI. - """ - - KINDS = {"property", "method", "class", "data", "event", "baseClass", - "innerClass", "interface", "mostDerivedClass", "virtual"} - ATTRIBUTES = {"static", "constant", "readOnly", "rawString", - "hasObjectId", "canHaveObjectId", "hasSideEffects"} - VISIBILITIES = {"public", "private", "protected", "internal", "final"} - FIELDS = [ - Field.START_OPTIONAL, - Field('kind', enum=KINDS), - Field('attributes', [Enum(str, ATTRIBUTES)]), - Field('visibility', enum=VISIBILITIES), - ] - - -class Variable(FieldsNamespace): - """A Variable is a name/value pair. - - Optionally a variable can have a 'type' that is shown if space - permits or when hovering over the variable's name. An optional - 'kind' is used to render additional properties of the variable, - e.g. different icons can be used to indicate that a variable is - public or private. If the value is structured (has children), a - handle is provided to retrieve the children with the - VariablesRequest. If the number of named or indexed children is - large, the numbers should be returned via the optional - 'namedVariables' and 'indexedVariables' attributes. The client can - use this optional information to present the children in a paged UI - and fetch them in chunks. - """ - - FIELDS = [ - Field('name'), - Field('value'), - Field.START_OPTIONAL, - Field('type'), - Field('presentationHint', VariablePresentationHint), - Field('evaluateName'), - Field('variablesReference', int, optional=False), - Field('namedVariables', int), - Field('indexedVariables', int), - ] - - -class SourceBreakpoint(FieldsNamespace): - """Properties of a breakpoint passed to the setBreakpoints request.""" - - FIELDS = [ - Field('line', int), - Field.START_OPTIONAL, - Field('column', int), - Field('condition'), - Field('hitCondition'), - ] - - -class FunctionBreakpoint(FieldsNamespace): - """ - Properties of a breakpoint passed to the setFunctionBreakpoints request. - """ - - FIELDS = [ - Field('name'), - Field.START_OPTIONAL, - Field('condition'), - Field('hitCondition'), - ] - - -class StepInTarget(FieldsNamespace): - """ - A StepInTarget can be used in the 'stepIn' request and determines - into which single target the stepIn request should step. - """ - - FIELDS = [ - Field('id', int), - Field('label'), - ] - - -class GotoTarget(FieldsNamespace): - """ - A GotoTarget describes a code location that can be used as a target - in the 'goto' request. The possible goto targets can be determined - via the 'gotoTargets' request. - """ - - FIELDS = [ - Field('id', int), - Field('label'), - Field('line', int), - Field.START_OPTIONAL, - Field('column', int), - Field('endLine', int), - Field('endColumn', int), - ] - - -class CompletionItem(FieldsNamespace): - """ - CompletionItems are the suggestions returned from the CompletionsRequest. - """ - - TYPES = {"method", "function", "constructor", "field", "variable", - "class", "interface", "module", "property", "unit", "value", - "enum", "keyword", "snippet", "text", "color", "file", - "reference", "customcolor"} - FIELDS = [ - Field('label'), - Field.START_OPTIONAL, - Field('text'), - Field('type'), - Field('start', int), - Field('length', int), - ] - - -class ValueFormat(FieldsNamespace): - """Provides formatting information for a value.""" - - FIELDS = [ - Field.START_OPTIONAL, - Field('hex', bool), - ] - - -class StackFrameFormat(ValueFormat): - """Provides formatting information for a stack frame.""" - - FIELDS = ValueFormat.FIELDS + [ - Field('parameters', bool), - Field('parameterTypes', bool), - Field('parameterNames', bool), - Field('parameterValues', bool), - Field('line', bool), - Field('module', bool), - Field('includeAll', bool), - ] - - -class ExceptionPathSegment(FieldsNamespace): - """ - An ExceptionPathSegment represents a segment in a path that is used - to match leafs or nodes in a tree of exceptions. If a segment - consists of more than one name, it matches the names provided if - 'negate' is false or missing or it matches anything except the names - provided if 'negate' is true. - """ - - FIELDS = [ - Field('negate', bool, optional=True), - Field('names', [str]), - ] - - -ExceptionBreakMode = Enum(str, - {"never", "always", "unhandled", "userUnhandled"}) - - -class ExceptionOptions(FieldsNamespace): - """ - An ExceptionOptions assigns configuration options to a set of exceptions. - """ - - FIELDS = [ - Field('path', [ExceptionPathSegment], optional=True), - Field('breakMode', ExceptionBreakMode), - ] - - -class ExceptionDetails(FieldsNamespace): - """Detailed information about an exception that has occurred.""" - - FIELDS = [ - Field.START_OPTIONAL, - Field('message'), - Field('typeName'), - Field('fullTypeName'), - Field('evaluateName'), - Field('stackTrace'), - Field('innerException', ['']), - ] diff --git a/debugger_protocol/messages/events.py b/debugger_protocol/messages/events.py deleted file mode 100644 index d3c98ea9..00000000 --- a/debugger_protocol/messages/events.py +++ /dev/null @@ -1,235 +0,0 @@ -from debugger_protocol.arg import ANY, FieldsNamespace, Field -from . import register -from .shared import Breakpoint, Module, Source -from .message import Event - - -@register -class InitializedEvent(Event): - """"Event message for 'initialized' event type. - - This event indicates that the debug adapter is ready to accept - configuration requests (e.g. SetBreakpointsRequest, - SetExceptionBreakpointsRequest). A debug adapter is expected to - send this event when it is ready to accept configuration requests - (but not before the InitializeRequest has finished). - - The sequence of events/requests is as follows: - - adapters sends InitializedEvent (after the InitializeRequest - has returned) - - frontend sends zero or more SetBreakpointsRequest - - frontend sends one SetFunctionBreakpointsRequest - - frontend sends a SetExceptionBreakpointsRequest if one or more - exceptionBreakpointFilters have been defined (or if - supportsConfigurationDoneRequest is not defined or false) - - frontend sends other future configuration requests - - frontend sends one ConfigurationDoneRequest to indicate the end - of the configuration - """ - - EVENT = 'initialized' - - -@register -class StoppedEvent(Event): - """Event message for 'stopped' event type. - - The event indicates that the execution of the debuggee has stopped - due to some condition. This can be caused by a break point - previously set, a stepping action has completed, by executing a - debugger statement etc. - """ - - EVENT = 'stopped' - - class BODY(FieldsNamespace): - REASONS = {'step', 'breakpoint', 'exception', 'pause', 'entry'} - FIELDS = [ - Field('reason', enum=REASONS), - Field.START_OPTIONAL, - Field('description'), - Field('threadId', int), - Field('text'), - Field('allThreadsStopped', bool), - ] - - -@register -class ContinuedEvent(Event): - """Event message for 'continued' event type. - - The event indicates that the execution of the debuggee has - continued. - - Please note: a debug adapter is not expected to send this event - in response to a request that implies that execution continues, - e.g. 'launch' or 'continue'. It is only necessary to send a - ContinuedEvent if there was no previous request that implied this. - """ - - EVENT = 'continued' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('threadId', int), - Field.START_OPTIONAL, - Field('allThreadsContinued', bool), - ] - - -@register -class ExitedEvent(Event): - """Event message for 'exited' event type. - - The event indicates that the debuggee has exited. - """ - - EVENT = 'exited' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('exitCode', int), - ] - - -@register -class TerminatedEvent(Event): - """Event message for 'terminated' event types. - - The event indicates that debugging of the debuggee has terminated. - """ - - EVENT = 'terminated' - - class BODY(FieldsNamespace): - FIELDS = [ - Field.START_OPTIONAL, - Field('restart', ANY), - ] - - -@register -class ThreadEvent(Event): - """Event message for 'thread' event type. - - The event indicates that a thread has started or exited. - """ - - EVENT = 'thread' - - class BODY(FieldsNamespace): - REASONS = {'started', 'exited'} - FIELDS = [ - Field('threadId', int), - Field('reason', enum=REASONS), - ] - - -@register -class OutputEvent(Event): - """Event message for 'output' event type. - - The event indicates that the target has produced some output. - """ - - EVENT = 'output' - - class BODY(FieldsNamespace): - CATEGORIES = {'console', 'stdout', 'stderr', 'telemetry'} - FIELDS = [ - Field('output'), - Field.START_OPTIONAL, - Field('category', enum=CATEGORIES), - Field('variablesReference', int), # "number" - Field('source'), - Field('line', int), - Field('column', int), - Field('data', ANY), - ] - - -@register -class BreakpointEvent(Event): - """Event message for 'breakpoint' event type. - - The event indicates that some information about a breakpoint - has changed. - """ - - EVENT = 'breakpoint' - - class BODY(FieldsNamespace): - REASONS = {'changed', 'new', 'removed'} - FIELDS = [ - Field('breakpoint', Breakpoint), - Field('reason', enum=REASONS), - ] - - -@register -class ModuleEvent(Event): - """Event message for 'module' event type. - - The event indicates that some information about a module - has changed. - """ - - EVENT = 'module' - - class BODY(FieldsNamespace): - REASONS = {'new', 'changed', 'removed'} - FIELDS = [ - Field('module', Module), - Field('reason', enum=REASONS), - ] - - -@register -class LoadedSourceEvent(Event): - """Event message for 'loadedSource' event type. - - The event indicates that some source has been added, changed, or - removed from the set of all loaded sources. - """ - - EVENT = 'loadedSource' - - class BODY(FieldsNamespace): - REASONS = {'new', 'changed', 'removed'} - FIELDS = [ - Field('source', Source), - Field('reason', enum=REASONS), - ] - - -@register -class ProcessEvent(Event): - """Event message for 'process' event type. - - The event indicates that the debugger has begun debugging a new - process. Either one that it has launched, or one that it has - attached to. - """ - - EVENT = 'process' - - class BODY(FieldsNamespace): - START_METHODS = {'launch', 'attach', 'attachForSuspendedLaunch'} - FIELDS = [ - Field('name'), - Field.START_OPTIONAL, - Field('systemProcessId', int), - Field('isLocalProcess', bool), - Field('startMethod', enum=START_METHODS), - ] - - -# Clean up the implicit __all__. -del register -del Event -del FieldsNamespace -del Field -del ANY -del Breakpoint -del Module -del Source diff --git a/debugger_protocol/messages/message.py b/debugger_protocol/messages/message.py deleted file mode 100644 index 3908aa40..00000000 --- a/debugger_protocol/messages/message.py +++ /dev/null @@ -1,366 +0,0 @@ -from debugger_protocol._base import Readonly, WithRepr -from debugger_protocol.arg import param_from_datatype -from . import MESSAGE_TYPES, Message - -""" -From the schema: - -MESSAGE = [ - name - base - description - props: [PROPERTY + (properties: [PROPERTY])] -] - -PROPERTY = [ - name - type: choices (one or a list) - (enum/_enum) - description - required: True/False (default: False) -] - -inheritance: override properties of base -""" - - -class ProtocolMessage(Readonly, WithRepr, Message): - """Base class of requests, responses, and events.""" - - _reqid = 0 - TYPE = None - - @classmethod - def from_data(cls, type, seq, **kwargs): - """Return an instance based on the given raw data.""" - return cls(type=type, seq=seq, **kwargs) - - @classmethod - def _next_reqid(cls): - reqid = ProtocolMessage._reqid - ProtocolMessage._reqid += 1 - return reqid - - _NOT_SET = object() - - def __init__(self, seq=_NOT_SET, **kwargs): - type = kwargs.pop('type', self.TYPE) - if seq is self._NOT_SET: - seq = self._next_reqid() - self._bind_attrs( - type=type or None, - seq=int(seq) if seq or seq == 0 else None, - ) - self._validate() - - def _validate(self): - if self.type is None: - raise TypeError('missing type') - elif self.TYPE is not None and self.type != self.TYPE: - raise ValueError('type must be {!r}'.format(self.TYPE)) - elif self.type not in MESSAGE_TYPES: - raise ValueError('unsupported type {!r}'.format(self.type)) - - if self.seq is None: - raise TypeError('missing seq') - elif self.seq < 0: - msg = '"seq" must be a non-negative int, got {!r}' - raise ValueError(msg.format(self.seq)) - - def _init_args(self): - if self.TYPE is None: - yield ('type', self.type) - yield ('seq', self.seq) - - def as_data(self): - """Return serializable data for the instance.""" - data = { - 'type': self.type, - 'seq': self.seq, - } - return data - - -################################## - -class Request(ProtocolMessage): - """A client or server-initiated request.""" - - TYPE = 'request' - TYPE_KEY = 'command' - - COMMAND = None - ARGUMENTS = None - ARGUMENTS_REQUIRED = None - - @classmethod - def from_data(cls, type, seq, command, arguments=None): - """Return an instance based on the given raw data.""" - return super(Request, cls).from_data( - type, seq, - command=command, - arguments=arguments, - ) - - @classmethod - def _arguments_required(cls): - if cls.ARGUMENTS_REQUIRED is None: - return cls.ARGUMENTS is not None - return cls.ARGUMENTS_REQUIRED - - def __init__(self, arguments=None, **kwargs): - command = kwargs.pop('command', self.COMMAND) - args = None - if arguments is not None: - try: - arguments = dict(arguments) - except TypeError: - pass - if self.ARGUMENTS is not None: - param = param_from_datatype(self.ARGUMENTS) - args = param.bind(arguments) - if args is None: - raise TypeError('bad arguments {!r}'.format(arguments)) - arguments = args.coerce() - self._bind_attrs( - command=command or None, - arguments=arguments or None, - _args=args, - ) - super(Request, self).__init__(**kwargs) - - def _validate(self): - super(Request, self)._validate() - - if self.command is None: - raise TypeError('missing command') - elif self.COMMAND is not None and self.command != self.COMMAND: - raise ValueError('command must be {!r}'.format(self.COMMAND)) - - if self.arguments is None: - if self._arguments_required(): - raise TypeError('missing arguments') - else: - if self.ARGUMENTS is None: - raise TypeError('got unexpected arguments') - self._args.validate() - - def _init_args(self): - if self.COMMAND is None: - yield ('command', self.command) - if self.arguments is not None: - yield ('arguments', self.arguments) - yield ('seq', self.seq) - - def as_data(self): - """Return serializable data for the instance.""" - data = super(Request, self).as_data() - data.update({ - 'command': self.command, - }) - if self.arguments is not None: - data.update({ - 'arguments': self.arguments.as_data(), - }) - return data - - -class Response(ProtocolMessage): - """Response to a request.""" - - TYPE = 'response' - TYPE_KEY = 'command' - - COMMAND = None - BODY = None - ERROR_BODY = None - BODY_REQUIRED = None - ERROR_BODY_REQUIRED = None - - @classmethod - def from_data(cls, type, seq, request_seq, command, success, - body=None, message=None): - """Return an instance based on the given raw data.""" - return super(Response, cls).from_data( - type, seq, - request_seq=request_seq, - command=command, - success=success, - body=body, - message=message, - ) - - @classmethod - def _body_required(cls, success=True): - required = cls.BODY_REQUIRED if success else cls.ERROR_BODY_REQUIRED - if required is not None: - return required - bodyclass = cls.BODY if success else cls.ERROR_BODY - return bodyclass is not None - - def __init__(self, request_seq, body=None, message=None, success=True, - **kwargs): - command = kwargs.pop('command', self.COMMAND) - reqseq = request_seq - bodyarg = None - if body is not None: - try: - body = dict(body) - except TypeError: - pass - bodyclass = self.BODY if success else self.ERROR_BODY - if bodyclass is not None: - param = param_from_datatype(bodyclass) - bodyarg = param.bind(body) - if bodyarg is None: - raise TypeError('bad body type {!r}'.format(body)) - body = bodyarg.coerce() - self._bind_attrs( - command=command or None, - request_seq=int(reqseq) if reqseq or reqseq == 0 else None, - body=body or None, - _bodyarg=bodyarg, - message=message or None, - success=bool(success), - ) - super(Response, self).__init__(**kwargs) - - def _validate(self): - super(Response, self)._validate() - - if self.request_seq is None: - raise TypeError('missing request_seq') - elif self.request_seq < 0: - msg = 'request_seq must be a non-negative int, got {!r}' - raise ValueError(msg.format(self.request_seq)) - - if not self.command: - raise TypeError('missing command') - elif self.COMMAND is not None and self.command != self.COMMAND: - raise ValueError('command must be {!r}'.format(self.COMMAND)) - - if self.body is None: - if self._body_required(self.success): - raise TypeError('missing body') - elif self._bodyarg is None: - raise ValueError('got unexpected body') - else: - self._bodyarg.validate() - - if not self.success and not self.message: - raise TypeError('missing message') - - def _init_args(self): - if self.COMMAND is None: - yield ('command', self.command) - yield ('request_seq', self.request_seq) - yield ('success', self.success) - if not self.success: - yield ('message', self.message) - if self.body is not None: - yield ('body', self.body) - yield ('seq', self.seq) - - def as_data(self): - """Return serializable data for the instance.""" - data = super(Response, self).as_data() - data.update({ - 'request_seq': self.request_seq, - 'command': self.command, - 'success': self.success, - }) - if self.body is not None: - data.update({ - 'body': self.body.as_data(), - }) - if self.message is not None: - data.update({ - 'message': self.message, - }) - return data - - -################################## - -class Event(ProtocolMessage): - """Server-initiated event.""" - - TYPE = 'event' - TYPE_KEY = 'event' - - EVENT = None - BODY = None - BODY_REQUIRED = None - - @classmethod - def from_data(cls, type, seq, event, body=None): - """Return an instance based on the given raw data.""" - return super(Event, cls).from_data(type, seq, event=event, body=body) - - @classmethod - def _body_required(cls): - if cls.BODY_REQUIRED is None: - return cls.BODY is not None - return cls.BODY_REQUIRED - - def __init__(self, body=None, **kwargs): - event = kwargs.pop('event', self.EVENT) - bodyarg = None - if body is not None: - try: - body = dict(body) - except TypeError: - pass - if self.BODY is not None: - param = param_from_datatype(self.BODY) - bodyarg = param.bind(body) - if bodyarg is None: - raise TypeError('bad body type {!r}'.format(body)) - body = bodyarg.coerce() - - self._bind_attrs( - event=event or None, - body=body or None, - _bodyarg=bodyarg, - ) - super(Event, self).__init__(**kwargs) - - def _validate(self): - super(Event, self)._validate() - - if self.event is None: - raise TypeError('missing event') - if self.EVENT is not None and self.event != self.EVENT: - msg = 'event must be {!r}, got {!r}' - raise ValueError(msg.format(self.EVENT, self.event)) - - if self.body is None: - if self._body_required(): - raise TypeError('missing body') - elif self._bodyarg is None: - raise ValueError('got unexpected body') - else: - self._bodyarg.validate() - - def _init_args(self): - if self.EVENT is None: - yield ('event', self.event) - if self.body is not None: - yield ('body', self.body) - yield ('seq', self.seq) - - @property - def name(self): - return self.event - - def as_data(self): - """Return serializable data for the instance.""" - data = super(Event, self).as_data() - data.update({ - 'event': self.event, - }) - if self.body is not None: - data.update({ - 'body': self.body.as_data(), - }) - return data diff --git a/debugger_protocol/messages/requests.py b/debugger_protocol/messages/requests.py deleted file mode 100644 index 61429f55..00000000 --- a/debugger_protocol/messages/requests.py +++ /dev/null @@ -1,1049 +0,0 @@ -from debugger_protocol.arg import FieldsNamespace, Field -from . import register -from . import _requests as datatypes -from . import shared -from .message import Request, Response - - -@register -class ErrorResponse(Response): - """An error response (for any unsuccessful request). - - On error (whenever 'success' is false) the body can provide more - details. - """ - - COMMAND = 'error' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('error', datatypes.Message), - ] - - -################################## - -@register -class RunInTerminalRequest(Request): - """runInTerminal request. - - With this request a debug adapter can run a command in a terminal. - """ - - COMMAND = 'runInTerminal' - - class ARGUMENTS(FieldsNamespace): - KINDS = {'integrated', 'external'} - FIELDS = [ - Field('kind', enum=KINDS, optional=True), - Field('title', optional=True), - Field('cwd'), - Field('args', [str]), - Field.START_OPTIONAL, - Field('env', {str: {str, None}}), - ] - - -@register -class RunInTerminalResponse(Response): - COMMAND = 'runInTerminal' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('processId', int), # number - ] - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class InitializeRequest(Request): - COMMAND = 'initialize' - - class ARGUMENTS(FieldsNamespace): - PATH_FORMATS = {'path', 'uri'} - FIELDS = [ - Field('clientID', optional=True), - Field('adapterID'), - Field.START_OPTIONAL, - Field('locale'), - Field('linesStartAt1', bool), - Field('columnsStartAt1', bool), - Field('pathFormat', enum=PATH_FORMATS), - Field('supportsVariableType', bool), - Field('supportsVariablePaging', bool), - Field('supportsRunInTerminalRequest', bool), - ] - - -@register -class InitializeResponse(Response): - COMMAND = 'initialize' - - BODY = datatypes.Capabilities - BODY_REQUIRED = False - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class ConfigurationDoneRequest(Request): - """configurationDone request. - - The client of the debug protocol must send this request at the end - of the sequence of configuration requests (which was started by - the InitializedEvent). - """ - - COMMAND = 'configurationDone' - - ARGUMENTS_REQUIRED = False - - -@register -class ConfigurationDoneResponse(Response): - COMMAND = 'configurationDone' - - # This is just an acknowledgement. - BODY_REQUIRED = False - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class LaunchRequest(Request): - COMMAND = 'launch' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('noDebug', bool, optional=True, default=False), - ] - - -@register -class LaunchResponse(Response): - COMMAND = 'launch' - - # This is just an acknowledgement. - BODY_REQUIRED = False - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class AttachRequest(Request): - COMMAND = 'attach' - - ARGUMENTS_REQUIRED = False - #class ARGUMENTS(FieldsNamespace): - # FIELDS = [] - - -@register -class AttachResponse(Response): - COMMAND = 'attach' - - # This is just an acknowledgement. - BODY_REQUIRED = False - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class RestartRequest(Request): - """restart request. - - Restarts a debug session. If the capability 'supportsRestartRequest' - is missing or has the value false, the client will implement - 'restart' by terminating the debug adapter first and then launching - it anew. A debug adapter can override this default behaviour by - implementing a restart request and setting the capability - 'supportsRestartRequest' to true. - """ - - COMMAND = 'restart' - - ARGUMENTS_REQUIRED = False - - -@register -class RestartResponse(Response): - COMMAND = 'restart' - - # This is just an acknowledgement. - BODY_REQUIRED = False - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class DisconnectRequest(Request): - """disconnect request. - - terminateDebuggee: Indicates whether the debuggee should be - terminated when the debugger is disconnected. If unspecified, - the debug adapter is free to do whatever it thinks is best. A - client can only rely on this attribute being properly honored if - a debug adapter returns true for the 'supportTerminateDebuggee' - capability. - """ - - COMMAND = 'disconnect' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('terminateDebuggee', bool, optional=True), - ] - - -@register -class DisconnectResponse(Response): - COMMAND = 'disconnect' - - # This is just an acknowledgement. - BODY_REQUIRED = False - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class SetBreakpointsRequest(Request): - """setBreakpoints request. - - Sets multiple breakpoints for a single source and clears all - previous breakpoints in that source. To clear all breakpoint for - a source, specify an empty array. When a breakpoint is hit, a - StoppedEvent (event type 'breakpoint') is generated. - """ - - COMMAND = 'setBreakpoints' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('source', shared.Source), - Field.START_OPTIONAL, - Field('breakpoints', [datatypes.SourceBreakpoint]), - Field('lines', [int]), - Field('sourceModified', bool), - ] - - -@register -class SetBreakpointsResponse(Response): - COMMAND = 'setBreakpoints' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('breakpoints', [shared.Breakpoint]), - ] - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class SetFunctionBreakpointsRequest(Request): - """setFunctionBreakpoints request. - - Sets multiple function breakpoints and clears all previous function - breakpoints. To clear all function breakpoint, specify an empty - array. When a function breakpoint is hit, a StoppedEvent (event - type 'function breakpoint') is generated. - """ - - COMMAND = 'setFunctionBreakpoints' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('breakpoints', [datatypes.FunctionBreakpoint]), - ] - - -@register -class SetFunctionBreakpointsResponse(Response): - COMMAND = 'setFunctionBreakpoints' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('breakpoints', [shared.Breakpoint]), - ] - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class SetExceptionBreakpointsRequest(Request): - """setExceptionBreakpoints request. - - The request configures the debuggers response to thrown exceptions. - If an exception is configured to break, a StoppedEvent is fired - (event type 'exception'). - """ - - COMMAND = 'setExceptionBreakpoints' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('filters', [str]), - Field.START_OPTIONAL, - Field('exceptionOptions', [datatypes.ExceptionOptions]), - ] - - -@register -class SetExceptionBreakpointsResponse(Response): - COMMAND = 'setExceptionBreakpoints' - - # This is just an acknowledgement. - BODY_REQUIRED = False - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class ContinueRequest(Request): - """continue request. - - The request starts the debuggee to run again. - - threadId: Continue execution for the specified thread (if possible). - If the backend cannot continue on a single thread but will - continue on all threads, it should set the allThreadsContinued - attribute in the response to true. - """ - - COMMAND = 'continue' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('threadId', int), - ] - - -@register -class ContinueResponse(Response): - COMMAND = 'continue' - - class BODY(FieldsNamespace): - FIELDS = [ - Field.START_OPTIONAL, - Field('allThreadsContinued', bool), - ] - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class NextRequest(Request): - """next request. - - The request starts the debuggee to run again for one step. The - debug adapter first sends the NextResponse and then a StoppedEvent - (event type 'step') after the step has completed. - """ - - COMMAND = 'next' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('threadId', int), - ] - - -@register -class NextResponse(Response): - COMMAND = 'next' - - # This is just an acknowledgement. - BODY_REQUIRED = False - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class StepInRequest(Request): - """stepIn request. - - The request starts the debuggee to step into a function/method if - possible. If it cannot step into a target, 'stepIn' behaves like - 'next'. The debug adapter first sends the StepInResponse and then - a StoppedEvent (event type 'step') after the step has completed. - If there are multiple function/method calls (or other targets) on - the source line, the optional argument 'targetId' can be used to - control into which target the 'stepIn' should occur. The list of - possible targets for a given source line can be retrieved via the - 'stepInTargets' request. - """ - - COMMAND = 'stepIn' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('threadId', int), - Field('targetId', int), - ] - - -@register -class StepInResponse(Response): - COMMAND = 'stepIn' - - # This is just an acknowledgement. - BODY_REQUIRED = False - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class StepOutRequest(Request): - """stepOut request. - - The request starts the debuggee to run again for one step. The - debug adapter first sends the StepOutResponse and then a - StoppedEvent (event type 'step') after the step has completed. - """ - - COMMAND = 'stepOut' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('threadId', int), - ] - - -@register -class StepOutResponse(Response): - COMMAND = 'stepOut' - - # This is just an acknowledgement. - BODY_REQUIRED = False - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class StepBackRequest(Request): - """stepBack request. - - The request starts the debuggee to run one step backwards. The - debug adapter first sends the StepBackResponse and then a - StoppedEvent (event type 'step') after the step has completed. - Clients should only call this request if the capability - supportsStepBack is true. - """ - - COMMAND = 'stepBack' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('threadId', int), - ] - - -@register -class StepBackResponse(Response): - COMMAND = 'stepBack' - - # This is just an acknowledgement. - BODY_REQUIRED = False - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class ReverseContinueRequest(Request): - """reverseContinue request. - - The request starts the debuggee to run backward. Clients should - only call this request if the capability supportsStepBack is true. - """ - - COMMAND = 'reverseContinue' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('threadId', int), - ] - - -@register -class ReverseContinueResponse(Response): - COMMAND = 'reverseContinue' - - # This is just an acknowledgement. - BODY_REQUIRED = False - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class RestartFrameRequest(Request): - """restartFrame request. - - The request restarts execution of the specified stackframe. The - debug adapter first sends the RestartFrameResponse and then a - StoppedEvent (event type 'restart') after the restart has - completed. - """ - - COMMAND = 'restartFrame' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('frameId', int), - ] - - -@register -class RestartFrameResponse(Response): - COMMAND = 'restartFrame' - - # This is just an acknowledgement. - BODY_REQUIRED = False - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class GotoRequest(Request): - """goto request. - - The request sets the location where the debuggee will continue to - run. This makes it possible to skip the execution of code or to - executed code again. The code between the current location and the - goto target is not executed but skipped. The debug adapter first - sends the GotoResponse and then a StoppedEvent (event type 'goto'). - """ - - COMMAND = 'goto' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('threadId', int), - Field('targetId', int), - ] - - -@register -class GotoResponse(Response): - COMMAND = 'goto' - - # This is just an acknowledgement. - BODY_REQUIRED = False - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class PauseRequest(Request): - """pause request. - - The request suspenses the debuggee. The debug adapter first sends - the PauseResponse and then a StoppedEvent (event type 'pause') - after the thread has been paused successfully. - """ - - COMMAND = 'pause' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('threadId', int), - ] - - -@register -class PauseResponse(Response): - COMMAND = 'pause' - - # This is just an acknowledgement. - BODY_REQUIRED = False - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class StackTraceRequest(Request): - """stackTrace request. - - The request returns a stacktrace from the current execution state. - """ - - COMMAND = 'stackTrace' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('threadId', int), - Field.START_OPTIONAL, - Field('startFrame', int), - Field('levels', int), - Field('format', datatypes.StackFrameFormat), - ] - - -@register -class StackTraceResponse(Response): - COMMAND = 'stackTrace' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('stackFrames', [datatypes.StackFrame]), - Field.START_OPTIONAL, - Field('totalFrames', int), - ] - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class ScopesRequest(Request): - """scopes request. - - The request returns the variable scopes for a given stackframe ID. - """ - - COMMAND = 'scopes' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('frameId', int), - ] - - -@register -class ScopesResponse(Response): - COMMAND = 'scopes' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('scopes', [datatypes.Scope]), - ] - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class VariablesRequest(Request): - """variables request. - - Retrieves all child variables for the given variable reference. An - optional filter can be used to limit the fetched children to either - named or indexed children. - """ - - COMMAND = 'variables' - - class ARGUMENTS(FieldsNamespace): - FILTERS = {'indexed', 'named'} - FIELDS = [ - Field('variablesReference', int), - Field.START_OPTIONAL, - Field('filter', enum=FILTERS), - Field('start', int), - Field('count', int), - Field('format', datatypes.ValueFormat), - ] - - -@register -class VariablesResponse(Response): - COMMAND = 'variables' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('variables', [datatypes.Variable]), - ] - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class SetVariableRequest(Request): - """setVariable request. - - Set the variable with the given name in the variable container - to a new value. - """ - - COMMAND = 'setVariable' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('variablesReference', int), - Field('name'), - Field('value'), - Field.START_OPTIONAL, - Field('format', datatypes.ValueFormat), - ] - - -@register -class SetVariableResponse(Response): - """ - """ - - COMMAND = 'setVariable' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('value'), - Field.START_OPTIONAL, - Field('type'), - Field('variablesReference', int), # number - Field('namedVariables', int), # number - Field('indexedVariables', int), # number - ] - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class SourceRequest(Request): - """source request. - - The request retrieves the source code for a given source reference. - """ - - COMMAND = 'source' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('source', shared.Source, optional=True), - Field('sourceReference', int), - ] - - -@register -class SourceResponse(Response): - COMMAND = 'source' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('content'), - Field.START_OPTIONAL, - Field('mimeType'), - ] - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class ThreadsRequest(Request): - """threads request. - - The request retrieves a list of all threads. - """ - - COMMAND = 'threads' - - ARGUMENTS_REQUIRED = False - - -@register -class ThreadsResponse(Response): - COMMAND = 'threads' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('threads', [datatypes.Thread]), - ] - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class ModulesRequest(Request): - """modules request. - - Modules can be retrieved from the debug adapter with the - ModulesRequest which can either return all modules or a range of - modules to support paging. - """ - - COMMAND = 'modules' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field.START_OPTIONAL, - Field('startModule', int, default=0), - Field('moduleCount', int), - ] - - -@register -class ModulesResponse(Response): - COMMAND = 'modules' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('modules', [shared.Module]), - Field.START_OPTIONAL, - Field('totalModules', int), - ] - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class LoadedSourcesRequest(Request): - """loadedSources request. - - Retrieves the set of all sources currently loaded by the debugged - process. - """ - - COMMAND = 'loadedSources' - - ARGUMENTS_REQUIRED = False - #class ARGUMENTS(FieldsNamespace): - # FIELDS = [] - - -@register -class LoadedSourcesResponse(Response): - COMMAND = 'loadedSources' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('sources', [shared.Source]), - ] - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class EvaluateRequest(Request): - """evaluate request. - - Evaluates the given expression in the context of the top most stack - frame. The expression has access to any variables and arguments - that are in scope. - """ - - COMMAND = 'evaluate' - - class ARGUMENTS(FieldsNamespace): - CONTEXTS = {'watch', 'repl', 'hover'} - FIELDS = [ - Field('expression'), - Field.START_OPTIONAL, - Field('frameId', int), - Field('context', enum=CONTEXTS), - Field('format', datatypes.ValueFormat), - ] - - -@register -class EvaluateResponse(Response): - COMMAND = 'evaluate' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('result'), - Field.START_OPTIONAL, - Field('type'), - Field('presentationHint', datatypes.VariablePresentationHint), - Field('variablesReference', int, optional=False), # number - Field('namedVariables', int), # number - Field('indexedVariables', int), # number - ] - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class StepInTargetsRequest(Request): - """stepInTargets request. - - This request retrieves the possible stepIn targets for the specified - stack frame. These targets can be used in the 'stepIn' request. - The StepInTargets may only be called if the - 'supportsStepInTargetsRequest' capability exists and is true. - """ - - COMMAND = 'stepInTargets' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('frameId', int), - ] - - -@register -class StepInTargetsResponse(Response): - COMMAND = 'stepInTargets' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('targets', [datatypes.StepInTarget]), - ] - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class GotoTargetsRequest(Request): - """gotoTargets request. - - This request retrieves the possible goto targets for the specified - source location. These targets can be used in the 'goto' request. - The GotoTargets request may only be called if the - 'supportsGotoTargetsRequest' capability exists and is true. - """ - - COMMAND = 'gotoTargets' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('source', shared.Source), - Field('line', int), - Field.START_OPTIONAL, - Field('column', int), - ] - - -@register -class GotoTargetsResponse(Response): - COMMAND = 'gotoTargets' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('targets', [datatypes.GotoTarget]), - ] - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class CompletionsRequest(Request): - """completions request. - - Returns a list of possible completions for a given caret position - and text. The CompletionsRequest may only be called if the - 'supportsCompletionsRequest' capability exists and is true. - """ - - COMMAND = 'completions' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('frameId', int, optional=True), - Field('text'), - Field('column', int), - Field.START_OPTIONAL, - Field('line', int), - ] - - -@register -class CompletionsResponse(Response): - COMMAND = 'completions' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('targets', [datatypes.CompletionItem]), - ] - - ERROR_BODY = ErrorResponse.BODY - - -################################## - -@register -class ExceptionInfoRequest(Request): - """exceptionInfo request. - - Retrieves the details of the exception that caused the StoppedEvent - to be raised. - """ - - COMMAND = 'exceptionInfo' - - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('threadId', int), - ] - - -@register -class ExceptionInfoResponse(Response): - COMMAND = 'exceptionInfo' - - class BODY(FieldsNamespace): - FIELDS = [ - Field('exceptionId'), - Field('description', optional=True), - Field('breakMode', datatypes.ExceptionBreakMode), - Field.START_OPTIONAL, - Field('details', datatypes.ExceptionDetails), - ] - - ERROR_BODY = ErrorResponse.BODY - - -# Clean up the implicit __all__. -del register -del Request -del Response -del FieldsNamespace -del Field -del datatypes -del shared diff --git a/debugger_protocol/messages/shared.py b/debugger_protocol/messages/shared.py deleted file mode 100644 index 5331b2ea..00000000 --- a/debugger_protocol/messages/shared.py +++ /dev/null @@ -1,85 +0,0 @@ -from debugger_protocol.arg import ANY, FieldsNamespace, Field - - -class Checksum(FieldsNamespace): - """The checksum of an item calculated by the specified algorithm.""" - - ALGORITHMS = {'MD5', 'SHA1', 'SHA256', 'timestamp'} - - FIELDS = [ - Field('algorithm', enum=ALGORITHMS), - Field('checksum'), - ] - - -class Source(FieldsNamespace): - """A Source is a descriptor for source code. - - It is returned from the debug adapter as part of a StackFrame - and it is used by clients when specifying breakpoints. - """ - - HINTS = {'normal', 'emphasize', 'deemphasize'} - - FIELDS = [ - Field.START_OPTIONAL, - Field('name'), - Field('path'), - Field('sourceReference', int), # number - Field('presentationHint', enum=HINTS), - Field('origin'), - Field('sources', ['']), - Field('adapterData', ANY), - Field('checksums', [Checksum]), - ] - - -class Breakpoint(FieldsNamespace): - """Information about a Breakpoint. - - The breakpoint comes from setBreakpoints or setFunctionBreakpoints. - """ - - FIELDS = [ - Field('id', int, optional=True), - Field('verified', bool), - Field.START_OPTIONAL, - Field('message'), - Field('source', Source), - Field('line', int), - Field('column', int), - Field('endLine', int), - Field('endColumn', int), - ] - - -class Module(FieldsNamespace): - """A Module object represents a row in the modules view. - - Two attributes are mandatory: an id identifies a module in the - modules view and is used in a ModuleEvent for identifying a module - for adding, updating or deleting. The name is used to minimally - render the module in the UI. - - Additional attributes can be added to the module. They will show up - in the module View if they have a corresponding ColumnDescriptor. - - To avoid an unnecessary proliferation of additional attributes with - similar semantics but different names we recommend to re-use - attributes from the 'recommended' list below first, and only - introduce new attributes if nothing appropriate could be found. - """ - - FIELDS = [ - Field('id', {int, str}), - Field('name'), - Field.START_OPTIONAL, - Field('path'), - Field('isOptimized', bool), - Field('isUserCode', bool), - Field('version'), - Field('symbolStatus'), - Field('symbolFilePath'), - Field('dateTimeStamp'), - Field('addressRange'), - ] diff --git a/debugger_protocol/messages/wireformat.py b/debugger_protocol/messages/wireformat.py deleted file mode 100644 index 630e0f34..00000000 --- a/debugger_protocol/messages/wireformat.py +++ /dev/null @@ -1,57 +0,0 @@ -import json - -from . import look_up - - -def read(stream, look_up=look_up): - """Return an instance based on the given bytes.""" - headers = {} - for line in stream: - if line == b'\r\n': - break - assert(line.endswith(b'\r\n')) - line = line[:-2].decode('ascii') - try: - name, value = line.split(': ', 1) - except ValueError: - raise RuntimeError('invalid header line: {}'.format(line)) - headers[name] = value - else: - # EOF - return None - - if not headers: - raise RuntimeError('got message without headers') - - size = int(headers['Content-Length']) - body = stream.read(size) - - data = json.loads(body.decode('utf-8')) - - cls = look_up(data) - return cls.from_data(**data) - - -def write(stream, msg): - """Serialize the message and write it to the stream.""" - raw = as_bytes(msg) - stream.write(raw) - - -def as_bytes(msg): - """Return the raw bytes for the message.""" - headers, body = _as_http_data(msg) - headers = '\r\n'.join('{}: {}'.format(name, value) - for name, value in headers.items()) - return headers.encode('ascii') + b'\r\n\r\n' + body.encode('utf-8') - - -def _as_http_data(msg): - payload = msg.as_data() - body = json.dumps(payload) - - headers = { - 'Content-Length': len(body), - 'Content-Type': 'application/json', - } - return headers, body diff --git a/debugger_protocol/schema/UPSTREAM b/debugger_protocol/schema/UPSTREAM deleted file mode 100644 index bc06f6b9..00000000 --- a/debugger_protocol/schema/UPSTREAM +++ /dev/null @@ -1,4 +0,0 @@ -upstream: https://github.com/Microsoft/vscode-debugadapter-node/raw/master/debugProtocol.json -revision: 1b9a3e83656ebb88f4560bb6d700f9ac64b697ba -checksum: 55a768cf61fe0c05c8af6c680b56154a -downloaded: 2018-09-06 14:00:00 (UTC) diff --git a/debugger_protocol/schema/__init__.py b/debugger_protocol/schema/__init__.py deleted file mode 100644 index 7967ce5f..00000000 --- a/debugger_protocol/schema/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import os.path - - -DATA_DIR = os.path.dirname(__file__) diff --git a/debugger_protocol/schema/__main__.py b/debugger_protocol/schema/__main__.py deleted file mode 100644 index e0b3b111..00000000 --- a/debugger_protocol/schema/__main__.py +++ /dev/null @@ -1,103 +0,0 @@ -import argparse -import sys - -from ._util import open_url -from .metadata import open_metadata -from .upstream import URL as UPSTREAM, download -from .vendored import FILENAME as VENDORED, check_local, check_upstream - - -COMMANDS = {} - - -def as_command(name): - def decorator(f): - COMMANDS[name] = f - return f - return decorator - - -@as_command('download') -def handle_download(source=UPSTREAM, target=VENDORED, *, - _open=open, _open_url=open_url): - # Download the schema file. - print('downloading the schema file from {}...'.format(source)) - with _open_url(source) as infile: - with _open(target, 'wb') as outfile: - meta = download(source, infile, outfile, - _open_url=_open_url) - print('...schema file written to {}.'.format(target)) - - # Save the metadata. - print('saving the schema metadata...') - formatted = meta.format() - metafile, filename = open_metadata(target, 'w', - _open=_open) - with metafile: - metafile.write(formatted) - print('...metadata written to {}.'.format(filename)) - - -@as_command('check') -def handle_check(schemafile=VENDORED, upstream=None, *, - _open=open, _open_url=open_url): - print('checking local schema file...') - try: - check_local(schemafile, - _open=_open) - except Exception as exc: - sys.exit('ERROR: {}'.format(exc)) - print('comparing with upstream schema file...') - try: - check_upstream(schemafile, url=upstream, - _open=_open, _open_url=_open_url) - except Exception as exc: - sys.exit('ERROR: {}'.format(exc)) - print('schema file okay') - - -############################# -# the script - -def parse_args(argv=sys.argv[1:], prog=None): - if prog is None: - if __name__ == '__main__': - module = __spec__.name - pkg, _, mod = module.rpartition('.') - if not pkg: - module = mod - elif mod == '__main__': - module = pkg - prog = 'python3 -m {}'.format(module) - else: - prog = sys.argv[0] - - parser = argparse.ArgumentParser( - prog=prog, - description='Manage the vendored VSC debugger protocol schema.', - ) - subs = parser.add_subparsers(dest='command') - - download = subs.add_parser('download') - download.add_argument('--source', default=UPSTREAM) - download.add_argument('--target', default=VENDORED) - - check = subs.add_parser('check') - check.add_argument('--schemafile', default=VENDORED) - check.add_argument('--upstream', default=None) - - args = parser.parse_args(argv) - if args.command is None: - parser.print_help() - parser.exit() - return args - - -def main(command, **kwargs): - handle_command = COMMANDS[command] - return handle_command(**kwargs) - - -if __name__ == '__main__': - args = parse_args() - main(**(vars(args))) diff --git a/debugger_protocol/schema/_util.py b/debugger_protocol/schema/_util.py deleted file mode 100644 index 84feba8e..00000000 --- a/debugger_protocol/schema/_util.py +++ /dev/null @@ -1,60 +0,0 @@ -import hashlib -import json -import re -import urllib.request - - -def open_url(url): - """Return a file-like object for (binary) reading the given URL.""" - return urllib.request.urlopen(url) - - -def get_revision(url, *, _open_url=open_url): - """Return the revision corresponding to the given URL.""" - if url.startswith('https://github.com/'): - return github_get_revision(url, _open_url=_open_url) - else: - return '' - - -def get_checksum(data): - """Return the MD5 hash for the given data.""" - m = hashlib.md5() - m.update(data) - return m.hexdigest() - - -################################## -# github - -GH_RESOURCE_RE = re.compile(r'^https://github.com' - r'/(?P[^/]*)' - r'/(?P[^/]*)' - r'/(?P[^/]*)' - r'/(?P[^/]*)' - r'/(?P.*)$') - - -def github_get_revision(url, *, _open_url=open_url): - """Return the full commit hash corresponding to the given URL.""" - m = GH_RESOURCE_RE.match(url) - if not m: - raise ValueError('invalid GitHub resource URL: {!r}'.format(url)) - org, repo, _, ref, path = m.groups() - - revurl = ('https://api.github.com/repos/{}/{}/commits?sha={}&path={}' - ).format(org, repo, ref, path) - with _open_url(revurl) as revinfo: - raw = revinfo.read() - data = json.loads(raw.decode()) - return data[0]['sha'] - - -def github_url_replace_ref(url, newref): - """Return a new URL with the ref replaced.""" - m = GH_RESOURCE_RE.match(url) - if not m: - raise ValueError('invalid GitHub resource URL: {!r}'.format(url)) - org, repo, kind, _, path = m.groups() - parts = (org, repo, kind, newref, path) - return 'https://github.com/{}/{}/{}/{}/{}'.format(*parts) diff --git a/debugger_protocol/schema/debugProtocol.json b/debugger_protocol/schema/debugProtocol.json deleted file mode 100644 index 1867da27..00000000 --- a/debugger_protocol/schema/debugProtocol.json +++ /dev/null @@ -1,3038 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Debug Adapter Protocol", - "description": "The Debug Adapter Protocol defines the protocol used between an editor or IDE and a debugger or runtime.", - "type": "object", - - - "definitions": { - - "ProtocolMessage": { - "type": "object", - "title": "Base Protocol", - "description": "Base class of requests, responses, and events.", - "properties": { - "seq": { - "type": "integer", - "description": "Sequence number." - }, - "type": { - "type": "string", - "description": "Message type.", - "_enum": [ "request", "response", "event" ] - } - }, - "required": [ "seq", "type" ] - }, - - "Request": { - "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, { - "type": "object", - "description": "A client or debug adapter initiated request.", - "properties": { - "type": { - "type": "string", - "enum": [ "request" ] - }, - "command": { - "type": "string", - "description": "The command to execute." - }, - "arguments": { - "type": [ "array", "boolean", "integer", "null", "number" , "object", "string" ], - "description": "Object containing arguments for the command." - } - }, - "required": [ "type", "command" ] - }] - }, - - "Event": { - "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, { - "type": "object", - "description": "A debug adapter initiated event.", - "properties": { - "type": { - "type": "string", - "enum": [ "event" ] - }, - "event": { - "type": "string", - "description": "Type of event." - }, - "body": { - "type": [ "array", "boolean", "integer", "null", "number" , "object", "string" ], - "description": "Event-specific information." - } - }, - "required": [ "type", "event" ] - }] - }, - - "Response": { - "allOf": [ { "$ref": "#/definitions/ProtocolMessage" }, { - "type": "object", - "description": "Response for a request.", - "properties": { - "type": { - "type": "string", - "enum": [ "response" ] - }, - "request_seq": { - "type": "integer", - "description": "Sequence number of the corresponding request." - }, - "success": { - "type": "boolean", - "description": "Outcome of the request." - }, - "command": { - "type": "string", - "description": "The command requested." - }, - "message": { - "type": "string", - "description": "Contains error message if success == false." - }, - "body": { - "type": [ "array", "boolean", "integer", "null", "number" , "object", "string" ], - "description": "Contains request result if success is true and optional error details if success is false." - } - }, - "required": [ "type", "request_seq", "success", "command" ] - }] - }, - - "ErrorResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "On error (whenever 'success' is false), the body can provide more details.", - "properties": { - "body": { - "type": "object", - "properties": { - "error": { - "$ref": "#/definitions/Message", - "description": "An optional, structured error message." - } - } - } - }, - "required": [ "body" ] - }] - }, - - "InitializedEvent": { - "allOf": [ { "$ref": "#/definitions/Event" }, { - "type": "object", - "title": "Events", - "description": "This event indicates that the debug adapter is ready to accept configuration requests (e.g. SetBreakpointsRequest, SetExceptionBreakpointsRequest).\nA debug adapter is expected to send this event when it is ready to accept configuration requests (but not before the 'initialize' request has finished).\nThe sequence of events/requests is as follows:\n- adapters sends 'initialized' event (after the 'initialize' request has returned)\n- frontend sends zero or more 'setBreakpoints' requests\n- frontend sends one 'setFunctionBreakpoints' request\n- frontend sends a 'setExceptionBreakpoints' request if one or more 'exceptionBreakpointFilters' have been defined (or if 'supportsConfigurationDoneRequest' is not defined or false)\n- frontend sends other future configuration requests\n- frontend sends one 'configurationDone' request to indicate the end of the configuration.", - "properties": { - "event": { - "type": "string", - "enum": [ "initialized" ] - } - }, - "required": [ "event" ] - }] - }, - - "StoppedEvent": { - "allOf": [ { "$ref": "#/definitions/Event" }, { - "type": "object", - "description": "The event indicates that the execution of the debuggee has stopped due to some condition.\nThis can be caused by a break point previously set, a stepping action has completed, by executing a debugger statement etc.", - "properties": { - "event": { - "type": "string", - "enum": [ "stopped" ] - }, - "body": { - "type": "object", - "properties": { - "reason": { - "type": "string", - "description": "The reason for the event.\nFor backward compatibility this string is shown in the UI if the 'description' attribute is missing (but it must not be translated).", - "_enum": [ "step", "breakpoint", "exception", "pause", "entry", "goto" ] - }, - "description": { - "type": "string", - "description": "The full reason for the event, e.g. 'Paused on exception'. This string is shown in the UI as is and must be translated." - }, - "threadId": { - "type": "integer", - "description": "The thread which was stopped." - }, - "preserveFocusHint": { - "type": "boolean", - "description": "A value of true hints to the frontend that this event should not change the focus." - }, - "text": { - "type": "string", - "description": "Additional information. E.g. if reason is 'exception', text contains the exception name. This string is shown in the UI." - }, - "allThreadsStopped": { - "type": "boolean", - "description": "If 'allThreadsStopped' is true, a debug adapter can announce that all threads have stopped.\n- The client should use this information to enable that all threads can be expanded to access their stacktraces.\n- If the attribute is missing or false, only the thread with the given threadId can be expanded." - } - }, - "required": [ "reason" ] - } - }, - "required": [ "event", "body" ] - }] - }, - - "ContinuedEvent": { - "allOf": [ { "$ref": "#/definitions/Event" }, { - "type": "object", - "description": "The event indicates that the execution of the debuggee has continued.\nPlease note: a debug adapter is not expected to send this event in response to a request that implies that execution continues, e.g. 'launch' or 'continue'.\nIt is only necessary to send a 'continued' event if there was no previous request that implied this.", - "properties": { - "event": { - "type": "string", - "enum": [ "continued" ] - }, - "body": { - "type": "object", - "properties": { - "threadId": { - "type": "integer", - "description": "The thread which was continued." - }, - "allThreadsContinued": { - "type": "boolean", - "description": "If 'allThreadsContinued' is true, a debug adapter can announce that all threads have continued." - } - }, - "required": [ "threadId" ] - } - }, - "required": [ "event", "body" ] - }] - }, - - "ExitedEvent": { - "allOf": [ { "$ref": "#/definitions/Event" }, { - "type": "object", - "description": "The event indicates that the debuggee has exited and returns its exit code.", - "properties": { - "event": { - "type": "string", - "enum": [ "exited" ] - }, - "body": { - "type": "object", - "properties": { - "exitCode": { - "type": "integer", - "description": "The exit code returned from the debuggee." - } - }, - "required": [ "exitCode" ] - } - }, - "required": [ "event", "body" ] - }] - }, - - "TerminatedEvent": { - "allOf": [ { "$ref": "#/definitions/Event" }, { - "type": "object", - "description": "The event indicates that debugging of the debuggee has terminated. This does **not** mean that the debuggee itself has exited.", - "properties": { - "event": { - "type": "string", - "enum": [ "terminated" ] - }, - "body": { - "type": "object", - "properties": { - "restart": { - "type": [ "array", "boolean", "integer", "null", "number", "object", "string" ], - "description": "A debug adapter may set 'restart' to true (or to an arbitrary object) to request that the front end restarts the session.\nThe value is not interpreted by the client and passed unmodified as an attribute '__restart' to the 'launch' and 'attach' requests." - } - } - } - }, - "required": [ "event" ] - }] - }, - - "ThreadEvent": { - "allOf": [ { "$ref": "#/definitions/Event" }, { - "type": "object", - "description": "The event indicates that a thread has started or exited.", - "properties": { - "event": { - "type": "string", - "enum": [ "thread" ] - }, - "body": { - "type": "object", - "properties": { - "reason": { - "type": "string", - "description": "The reason for the event.", - "_enum": [ "started", "exited" ] - }, - "threadId": { - "type": "integer", - "description": "The identifier of the thread." - } - }, - "required": ["reason", "threadId"] - } - }, - "required": [ "event", "body" ] - }] - }, - - "OutputEvent": { - "allOf": [ { "$ref": "#/definitions/Event" }, { - "type": "object", - "description": "The event indicates that the target has produced some output.", - "properties": { - "event": { - "type": "string", - "enum": [ "output" ] - }, - "body": { - "type": "object", - "properties": { - "category": { - "type": "string", - "description": "The output category. If not specified, 'console' is assumed.", - "_enum": [ "console", "stdout", "stderr", "telemetry" ] - }, - "output": { - "type": "string", - "description": "The output to report." - }, - "variablesReference": { - "type": "number", - "description": "If an attribute 'variablesReference' exists and its value is > 0, the output contains objects which can be retrieved by passing 'variablesReference' to the 'variables' request." - }, - "source": { - "$ref": "#/definitions/Source", - "description": "An optional source location where the output was produced." - }, - "line": { - "type": "integer", - "description": "An optional source location line where the output was produced." - }, - "column": { - "type": "integer", - "description": "An optional source location column where the output was produced." - }, - "data": { - "type": [ "array", "boolean", "integer", "null", "number" , "object", "string" ], - "description": "Optional data to report. For the 'telemetry' category the data will be sent to telemetry, for the other categories the data is shown in JSON format." - } - }, - "required": ["output"] - } - }, - "required": [ "event", "body" ] - }] - }, - - "BreakpointEvent": { - "allOf": [ { "$ref": "#/definitions/Event" }, { - "type": "object", - "description": "The event indicates that some information about a breakpoint has changed.", - "properties": { - "event": { - "type": "string", - "enum": [ "breakpoint" ] - }, - "body": { - "type": "object", - "properties": { - "reason": { - "type": "string", - "description": "The reason for the event.", - "_enum": [ "changed", "new", "removed" ] - }, - "breakpoint": { - "$ref": "#/definitions/Breakpoint", - "description": "The breakpoint." - } - }, - "required": [ "reason", "breakpoint" ] - } - }, - "required": [ "event", "body" ] - }] - }, - - "ModuleEvent": { - "allOf": [ { "$ref": "#/definitions/Event" }, { - "type": "object", - "description": "The event indicates that some information about a module has changed.", - "properties": { - "event": { - "type": "string", - "enum": [ "module" ] - }, - "body": { - "type": "object", - "properties": { - "reason": { - "type": "string", - "description": "The reason for the event.", - "enum": [ "new", "changed", "removed" ] - }, - "module": { - "$ref": "#/definitions/Module", - "description": "The new, changed, or removed module. In case of 'removed' only the module id is used." - } - }, - "required": [ "reason", "module" ] - } - }, - "required": [ "event", "body" ] - }] - }, - - "LoadedSourceEvent": { - "allOf": [ { "$ref": "#/definitions/Event" }, { - "type": "object", - "description": "The event indicates that some source has been added, changed, or removed from the set of all loaded sources.", - "properties": { - "event": { - "type": "string", - "enum": [ "loadedSource" ] - }, - "body": { - "type": "object", - "properties": { - "reason": { - "type": "string", - "description": "The reason for the event.", - "enum": [ "new", "changed", "removed" ] - }, - "source": { - "$ref": "#/definitions/Source", - "description": "The new, changed, or removed source." - } - }, - "required": [ "reason", "source" ] - } - }, - "required": [ "event", "body" ] - }] - }, - - "ProcessEvent": { - "allOf": [ - { "$ref": "#/definitions/Event" }, - { - "type": "object", - "description": "The event indicates that the debugger has begun debugging a new process. Either one that it has launched, or one that it has attached to.", - "properties": { - "event": { - "type": "string", - "enum": [ "process" ] - }, - "body": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The logical name of the process. This is usually the full path to process's executable file. Example: /home/example/myproj/program.js." - }, - "systemProcessId": { - "type": "integer", - "description": "The system process id of the debugged process. This property will be missing for non-system processes." - }, - "isLocalProcess": { - "type": "boolean", - "description": "If true, the process is running on the same computer as the debug adapter." - }, - "startMethod": { - "type": "string", - "enum": [ "launch", "attach", "attachForSuspendedLaunch" ], - "description": "Describes how the debug engine started debugging this process.", - "enumDescriptions": [ - "Process was launched under the debugger.", - "Debugger attached to an existing process.", - "A project launcher component has launched a new process in a suspended state and then asked the debugger to attach." - ] - } - }, - "required": [ "name" ] - } - }, - "required": [ "event", "body" ] - } - ] - }, - - "CapabilitiesEvent": { - "allOf": [ { "$ref": "#/definitions/Event" }, { - "type": "object", - "description": "The event indicates that one or more capabilities have changed.\nSince the capabilities are dependent on the frontend and its UI, it might not be possible to change that at random times (or too late).\nConsequently this event has a hint characteristic: a frontend can only be expected to make a 'best effort' in honouring individual capabilities but there are no guarantees.\nOnly changed capabilities need to be included, all other capabilities keep their values.", - "properties": { - "event": { - "type": "string", - "enum": [ "capabilities" ] - }, - "body": { - "type": "object", - "properties": { - "capabilities": { - "$ref": "#/definitions/Capabilities", - "description": "The set of updated capabilities." - } - }, - "required": [ "capabilities" ] - } - }, - "required": [ "event", "body" ] - }] - }, - - "RunInTerminalRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "title": "Reverse Requests", - "description": "This request is sent from the debug adapter to the client to run a command in a terminal. This is typically used to launch the debuggee in a terminal provided by the client.", - "properties": { - "command": { - "type": "string", - "enum": [ "runInTerminal" ] - }, - "arguments": { - "$ref": "#/definitions/RunInTerminalRequestArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "RunInTerminalRequestArguments": { - "type": "object", - "description": "Arguments for 'runInTerminal' request.", - "properties": { - "kind": { - "type": "string", - "enum": [ "integrated", "external" ], - "description": "What kind of terminal to launch." - }, - "title": { - "type": "string", - "description": "Optional title of the terminal." - }, - "cwd": { - "type": "string", - "description": "Working directory of the command." - }, - "args": { - "type": "array", - "items": { - "type": "string" - }, - "description": "List of arguments. The first argument is the command to run." - }, - "env": { - "type": "object", - "description": "Environment key-value pairs that are added to or removed from the default environment.", - "additionalProperties": { - "type": [ "string", "null" ], - "description": "Proper values must be strings. A value of 'null' removes the variable from the environment." - } - } - }, - "required": [ "args", "cwd" ] - }, - "RunInTerminalResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'runInTerminal' request.", - "properties": { - "body": { - "type": "object", - "properties": { - "processId": { - "type": "number", - "description": "The process ID." - } - } - } - }, - "required": [ "body" ] - }] - }, - - "InitializeRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "title": "Requests", - "description": "The 'initialize' request is sent as the first request from the client to the debug adapter in order to configure it with client capabilities and to retrieve capabilities from the debug adapter.\nUntil the debug adapter has responded to with an 'initialize' response, the client must not send any additional requests or events to the debug adapter. In addition the debug adapter is not allowed to send any requests or events to the client until it has responded with an 'initialize' response.\nThe 'initialize' request may only be sent once.", - "properties": { - "command": { - "type": "string", - "enum": [ "initialize" ] - }, - "arguments": { - "$ref": "#/definitions/InitializeRequestArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "InitializeRequestArguments": { - "type": "object", - "description": "Arguments for 'initialize' request.", - "properties": { - "clientID": { - "type": "string", - "description": "The ID of the (frontend) client using this adapter." - }, - "clientName": { - "type": "string", - "description": "The human readable name of the (frontend) client using this adapter." - }, - "adapterID": { - "type": "string", - "description": "The ID of the debug adapter." - }, - "locale": { - "type": "string", - "description": "The ISO-639 locale of the (frontend) client using this adapter, e.g. en-US or de-CH." - }, - "linesStartAt1": { - "type": "boolean", - "description": "If true all line numbers are 1-based (default)." - }, - "columnsStartAt1": { - "type": "boolean", - "description": "If true all column numbers are 1-based (default)." - }, - "pathFormat": { - "type": "string", - "_enum": [ "path", "uri" ], - "description": "Determines in what format paths are specified. The default is 'path', which is the native format." - }, - "supportsVariableType": { - "type": "boolean", - "description": "Client supports the optional type attribute for variables." - }, - "supportsVariablePaging": { - "type": "boolean", - "description": "Client supports the paging of variables." - }, - "supportsRunInTerminalRequest": { - "type": "boolean", - "description": "Client supports the runInTerminal request." - } - }, - "required": [ "adapterID" ] - }, - "InitializeResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'initialize' request.", - "properties": { - "body": { - "$ref": "#/definitions/Capabilities", - "description": "The capabilities of this debug adapter." - } - } - }] - }, - - "ConfigurationDoneRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The client of the debug protocol must send this request at the end of the sequence of configuration requests (which was started by the 'initialized' event).", - "properties": { - "command": { - "type": "string", - "enum": [ "configurationDone" ] - }, - "arguments": { - "$ref": "#/definitions/ConfigurationDoneArguments" - } - }, - "required": [ "command" ] - }] - }, - "ConfigurationDoneArguments": { - "type": "object", - "description": "Arguments for 'configurationDone' request." - }, - "ConfigurationDoneResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'configurationDone' request. This is just an acknowledgement, so no body field is required." - }] - }, - - "LaunchRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The launch request is sent from the client to the debug adapter to start the debuggee with or without debugging (if 'noDebug' is true). Since launching is debugger/runtime specific, the arguments for this request are not part of this specification.", - "properties": { - "command": { - "type": "string", - "enum": [ "launch" ] - }, - "arguments": { - "$ref": "#/definitions/LaunchRequestArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "LaunchRequestArguments": { - "type": "object", - "description": "Arguments for 'launch' request. Additional attributes are implementation specific.", - "properties": { - "noDebug": { - "type": "boolean", - "description": "If noDebug is true the launch request should launch the program without enabling debugging." - }, - "__restart": { - "type": [ "array", "boolean", "integer", "null", "number", "object", "string" ], - "description": "Optional data from the previous, restarted session.\nThe data is sent as the 'restart' attribute of the 'terminated' event.\nThe client should leave the data intact." - } - } - }, - "LaunchResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'launch' request. This is just an acknowledgement, so no body field is required." - }] - }, - - "AttachRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The attach request is sent from the client to the debug adapter to attach to a debuggee that is already running. Since attaching is debugger/runtime specific, the arguments for this request are not part of this specification.", - "properties": { - "command": { - "type": "string", - "enum": [ "attach" ] - }, - "arguments": { - "$ref": "#/definitions/AttachRequestArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "AttachRequestArguments": { - "type": "object", - "description": "Arguments for 'attach' request. Additional attributes are implementation specific.", - "properties": { - "__restart": { - "type": [ "array", "boolean", "integer", "null", "number", "object", "string" ], - "description": "Optional data from the previous, restarted session.\nThe data is sent as the 'restart' attribute of the 'terminated' event.\nThe client should leave the data intact." - } - } - }, - "AttachResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'attach' request. This is just an acknowledgement, so no body field is required." - }] - }, - - "RestartRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "Restarts a debug session. If the capability 'supportsRestartRequest' is missing or has the value false,\nthe client will implement 'restart' by terminating the debug adapter first and then launching it anew.\nA debug adapter can override this default behaviour by implementing a restart request\nand setting the capability 'supportsRestartRequest' to true.", - "properties": { - "command": { - "type": "string", - "enum": [ "restart" ] - }, - "arguments": { - "$ref": "#/definitions/RestartArguments" - } - }, - "required": [ "command" ] - }] - }, - "RestartArguments": { - "type": "object", - "description": "Arguments for 'restart' request." - }, - "RestartResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'restart' request. This is just an acknowledgement, so no body field is required." - }] - }, - - "DisconnectRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The 'disconnect' request is sent from the client to the debug adapter in order to stop debugging. It asks the debug adapter to disconnect from the debuggee and to terminate the debug adapter. If the debuggee has been started with the 'launch' request, the 'disconnect' request terminates the debuggee. If the 'attach' request was used to connect to the debuggee, 'disconnect' does not terminate the debuggee. This behavior can be controlled with the 'terminateDebuggee' (if supported by the debug adapter).", - "properties": { - "command": { - "type": "string", - "enum": [ "disconnect" ] - }, - "arguments": { - "$ref": "#/definitions/DisconnectArguments" - } - }, - "required": [ "command" ] - }] - }, - "DisconnectArguments": { - "type": "object", - "description": "Arguments for 'disconnect' request.", - "properties": { - "restart": { - "type": "boolean", - "description": "A value of true indicates that this 'disconnect' request is part of a restart sequence." - }, - "terminateDebuggee": { - "type": "boolean", - "description": "Indicates whether the debuggee should be terminated when the debugger is disconnected.\nIf unspecified, the debug adapter is free to do whatever it thinks is best.\nA client can only rely on this attribute being properly honored if a debug adapter returns true for the 'supportTerminateDebuggee' capability." - } - } - }, - "DisconnectResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'disconnect' request. This is just an acknowledgement, so no body field is required." - }] - }, - - "TerminateRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The 'terminate' request is sent from the client to the debug adapter in order to give the debuggee a chance for terminating itself.", - "properties": { - "command": { - "type": "string", - "enum": [ "terminate" ] - }, - "arguments": { - "$ref": "#/definitions/TerminateArguments" - } - }, - "required": [ "command" ] - }] - }, - "TerminateArguments": { - "type": "object", - "description": "Arguments for 'terminate' request.", - "properties": { - "restart": { - "type": "boolean", - "description": "A value of true indicates that this 'terminate' request is part of a restart sequence." - } - } - }, - "TerminateResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'terminate' request. This is just an acknowledgement, so no body field is required." - }] - }, - - "SetBreakpointsRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "Sets multiple breakpoints for a single source and clears all previous breakpoints in that source.\nTo clear all breakpoint for a source, specify an empty array.\nWhen a breakpoint is hit, a 'stopped' event (with reason 'breakpoint') is generated.", - "properties": { - "command": { - "type": "string", - "enum": [ "setBreakpoints" ] - }, - "arguments": { - "$ref": "#/definitions/SetBreakpointsArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "SetBreakpointsArguments": { - "type": "object", - "description": "Arguments for 'setBreakpoints' request.", - "properties": { - "source": { - "$ref": "#/definitions/Source", - "description": "The source location of the breakpoints; either 'source.path' or 'source.reference' must be specified." - }, - "breakpoints": { - "type": "array", - "items": { - "$ref": "#/definitions/SourceBreakpoint" - }, - "description": "The code locations of the breakpoints." - }, - "lines": { - "type": "array", - "items": { - "type": "integer" - }, - "description": "Deprecated: The code locations of the breakpoints." - }, - "sourceModified": { - "type": "boolean", - "description": "A value of true indicates that the underlying source has been modified which results in new breakpoint locations." - } - }, - "required": [ "source" ] - }, - "SetBreakpointsResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'setBreakpoints' request.\nReturned is information about each breakpoint created by this request.\nThis includes the actual code location and whether the breakpoint could be verified.\nThe breakpoints returned are in the same order as the elements of the 'breakpoints'\n(or the deprecated 'lines') array in the arguments.", - "properties": { - "body": { - "type": "object", - "properties": { - "breakpoints": { - "type": "array", - "items": { - "$ref": "#/definitions/Breakpoint" - }, - "description": "Information about the breakpoints. The array elements are in the same order as the elements of the 'breakpoints' (or the deprecated 'lines') array in the arguments." - } - }, - "required": [ "breakpoints" ] - } - }, - "required": [ "body" ] - }] - }, - - "SetFunctionBreakpointsRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "Sets multiple function breakpoints and clears all previous function breakpoints.\nTo clear all function breakpoint, specify an empty array.\nWhen a function breakpoint is hit, a 'stopped' event (event type 'function breakpoint') is generated.", - "properties": { - "command": { - "type": "string", - "enum": [ "setFunctionBreakpoints" ] - }, - "arguments": { - "$ref": "#/definitions/SetFunctionBreakpointsArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "SetFunctionBreakpointsArguments": { - "type": "object", - "description": "Arguments for 'setFunctionBreakpoints' request.", - "properties": { - "breakpoints": { - "type": "array", - "items": { - "$ref": "#/definitions/FunctionBreakpoint" - }, - "description": "The function names of the breakpoints." - } - }, - "required": [ "breakpoints" ] - }, - "SetFunctionBreakpointsResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'setFunctionBreakpoints' request.\nReturned is information about each breakpoint created by this request.", - "properties": { - "body": { - "type": "object", - "properties": { - "breakpoints": { - "type": "array", - "items": { - "$ref": "#/definitions/Breakpoint" - }, - "description": "Information about the breakpoints. The array elements correspond to the elements of the 'breakpoints' array." - } - }, - "required": [ "breakpoints" ] - } - }, - "required": [ "body" ] - }] - }, - - "SetExceptionBreakpointsRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The request configures the debuggers response to thrown exceptions. If an exception is configured to break, a 'stopped' event is fired (with reason 'exception').", - "properties": { - "command": { - "type": "string", - "enum": [ "setExceptionBreakpoints" ] - }, - "arguments": { - "$ref": "#/definitions/SetExceptionBreakpointsArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "SetExceptionBreakpointsArguments": { - "type": "object", - "description": "Arguments for 'setExceptionBreakpoints' request.", - "properties": { - "filters": { - "type": "array", - "items": { - "type": "string" - }, - "description": "IDs of checked exception options. The set of IDs is returned via the 'exceptionBreakpointFilters' capability." - }, - "exceptionOptions": { - "type": "array", - "items": { - "$ref": "#/definitions/ExceptionOptions" - }, - "description": "Configuration options for selected exceptions." - } - }, - "required": [ "filters" ] - }, - "SetExceptionBreakpointsResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'setExceptionBreakpoints' request. This is just an acknowledgement, so no body field is required." - }] - }, - - "ContinueRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The request starts the debuggee to run again.", - "properties": { - "command": { - "type": "string", - "enum": [ "continue" ] - }, - "arguments": { - "$ref": "#/definitions/ContinueArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "ContinueArguments": { - "type": "object", - "description": "Arguments for 'continue' request.", - "properties": { - "threadId": { - "type": "integer", - "description": "Continue execution for the specified thread (if possible). If the backend cannot continue on a single thread but will continue on all threads, it should set the 'allThreadsContinued' attribute in the response to true." - } - }, - "required": [ "threadId" ] - }, - "ContinueResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'continue' request.", - "properties": { - "body": { - "type": "object", - "properties": { - "allThreadsContinued": { - "type": "boolean", - "description": "If true, the 'continue' request has ignored the specified thread and continued all threads instead. If this attribute is missing a value of 'true' is assumed for backward compatibility." - } - } - } - }, - "required": [ "body" ] - }] - }, - - "NextRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The request starts the debuggee to run again for one step.\nThe debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed.", - "properties": { - "command": { - "type": "string", - "enum": [ "next" ] - }, - "arguments": { - "$ref": "#/definitions/NextArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "NextArguments": { - "type": "object", - "description": "Arguments for 'next' request.", - "properties": { - "threadId": { - "type": "integer", - "description": "Execute 'next' for this thread." - } - }, - "required": [ "threadId" ] - }, - "NextResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'next' request. This is just an acknowledgement, so no body field is required." - }] - }, - - "StepInRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The request starts the debuggee to step into a function/method if possible.\nIf it cannot step into a target, 'stepIn' behaves like 'next'.\nThe debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed.\nIf there are multiple function/method calls (or other targets) on the source line,\nthe optional argument 'targetId' can be used to control into which target the 'stepIn' should occur.\nThe list of possible targets for a given source line can be retrieved via the 'stepInTargets' request.", - "properties": { - "command": { - "type": "string", - "enum": [ "stepIn" ] - }, - "arguments": { - "$ref": "#/definitions/StepInArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "StepInArguments": { - "type": "object", - "description": "Arguments for 'stepIn' request.", - "properties": { - "threadId": { - "type": "integer", - "description": "Execute 'stepIn' for this thread." - }, - "targetId": { - "type": "integer", - "description": "Optional id of the target to step into." - } - }, - "required": [ "threadId" ] - }, - "StepInResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'stepIn' request. This is just an acknowledgement, so no body field is required." - }] - }, - - "StepOutRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The request starts the debuggee to run again for one step.\nThe debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed.", - "properties": { - "command": { - "type": "string", - "enum": [ "stepOut" ] - }, - "arguments": { - "$ref": "#/definitions/StepOutArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "StepOutArguments": { - "type": "object", - "description": "Arguments for 'stepOut' request.", - "properties": { - "threadId": { - "type": "integer", - "description": "Execute 'stepOut' for this thread." - } - }, - "required": [ "threadId" ] - }, - "StepOutResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'stepOut' request. This is just an acknowledgement, so no body field is required." - }] - }, - - "StepBackRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The request starts the debuggee to run one step backwards.\nThe debug adapter first sends the response and then a 'stopped' event (with reason 'step') after the step has completed. Clients should only call this request if the capability 'supportsStepBack' is true.", - "properties": { - "command": { - "type": "string", - "enum": [ "stepBack" ] - }, - "arguments": { - "$ref": "#/definitions/StepBackArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "StepBackArguments": { - "type": "object", - "description": "Arguments for 'stepBack' request.", - "properties": { - "threadId": { - "type": "integer", - "description": "Execute 'stepBack' for this thread." - } - }, - "required": [ "threadId" ] - }, - "StepBackResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'stepBack' request. This is just an acknowledgement, so no body field is required." - }] - }, - - "ReverseContinueRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The request starts the debuggee to run backward. Clients should only call this request if the capability 'supportsStepBack' is true.", - "properties": { - "command": { - "type": "string", - "enum": [ "reverseContinue" ] - }, - "arguments": { - "$ref": "#/definitions/ReverseContinueArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "ReverseContinueArguments": { - "type": "object", - "description": "Arguments for 'reverseContinue' request.", - "properties": { - "threadId": { - "type": "integer", - "description": "Execute 'reverseContinue' for this thread." - } - }, - "required": [ "threadId" ] - }, - "ReverseContinueResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'reverseContinue' request. This is just an acknowledgement, so no body field is required." - }] - }, - - "RestartFrameRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The request restarts execution of the specified stackframe.\nThe debug adapter first sends the response and then a 'stopped' event (with reason 'restart') after the restart has completed.", - "properties": { - "command": { - "type": "string", - "enum": [ "restartFrame" ] - }, - "arguments": { - "$ref": "#/definitions/RestartFrameArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "RestartFrameArguments": { - "type": "object", - "description": "Arguments for 'restartFrame' request.", - "properties": { - "frameId": { - "type": "integer", - "description": "Restart this stackframe." - } - }, - "required": [ "frameId" ] - }, - "RestartFrameResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'restartFrame' request. This is just an acknowledgement, so no body field is required." - }] - }, - - "GotoRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The request sets the location where the debuggee will continue to run.\nThis makes it possible to skip the execution of code or to executed code again.\nThe code between the current location and the goto target is not executed but skipped.\nThe debug adapter first sends the response and then a 'stopped' event with reason 'goto'.", - "properties": { - "command": { - "type": "string", - "enum": [ "goto" ] - }, - "arguments": { - "$ref": "#/definitions/GotoArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "GotoArguments": { - "type": "object", - "description": "Arguments for 'goto' request.", - "properties": { - "threadId": { - "type": "integer", - "description": "Set the goto target for this thread." - }, - "targetId": { - "type": "integer", - "description": "The location where the debuggee will continue to run." - } - }, - "required": [ "threadId", "targetId" ] - }, - "GotoResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'goto' request. This is just an acknowledgement, so no body field is required." - }] - }, - - "PauseRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The request suspenses the debuggee.\nThe debug adapter first sends the response and then a 'stopped' event (with reason 'pause') after the thread has been paused successfully.", - "properties": { - "command": { - "type": "string", - "enum": [ "pause" ] - }, - "arguments": { - "$ref": "#/definitions/PauseArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "PauseArguments": { - "type": "object", - "description": "Arguments for 'pause' request.", - "properties": { - "threadId": { - "type": "integer", - "description": "Pause execution for this thread." - } - }, - "required": [ "threadId" ] - }, - "PauseResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'pause' request. This is just an acknowledgement, so no body field is required." - }] - }, - - "StackTraceRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The request returns a stacktrace from the current execution state.", - "properties": { - "command": { - "type": "string", - "enum": [ "stackTrace" ] - }, - "arguments": { - "$ref": "#/definitions/StackTraceArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "StackTraceArguments": { - "type": "object", - "description": "Arguments for 'stackTrace' request.", - "properties": { - "threadId": { - "type": "integer", - "description": "Retrieve the stacktrace for this thread." - }, - "startFrame": { - "type": "integer", - "description": "The index of the first frame to return; if omitted frames start at 0." - }, - "levels": { - "type": "integer", - "description": "The maximum number of frames to return. If levels is not specified or 0, all frames are returned." - }, - "format": { - "$ref": "#/definitions/StackFrameFormat", - "description": "Specifies details on how to format the stack frames." - } - }, - "required": [ "threadId" ] - }, - "StackTraceResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'stackTrace' request.", - "properties": { - "body": { - "type": "object", - "properties": { - "stackFrames": { - "type": "array", - "items": { - "$ref": "#/definitions/StackFrame" - }, - "description": "The frames of the stackframe. If the array has length zero, there are no stackframes available.\nThis means that there is no location information available." - }, - "totalFrames": { - "type": "integer", - "description": "The total number of frames available." - } - }, - "required": [ "stackFrames" ] - } - }, - "required": [ "body" ] - }] - }, - - "ScopesRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The request returns the variable scopes for a given stackframe ID.", - "properties": { - "command": { - "type": "string", - "enum": [ "scopes" ] - }, - "arguments": { - "$ref": "#/definitions/ScopesArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "ScopesArguments": { - "type": "object", - "description": "Arguments for 'scopes' request.", - "properties": { - "frameId": { - "type": "integer", - "description": "Retrieve the scopes for this stackframe." - } - }, - "required": [ "frameId" ] - }, - "ScopesResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'scopes' request.", - "properties": { - "body": { - "type": "object", - "properties": { - "scopes": { - "type": "array", - "items": { - "$ref": "#/definitions/Scope" - }, - "description": "The scopes of the stackframe. If the array has length zero, there are no scopes available." - } - }, - "required": [ "scopes" ] - } - }, - "required": [ "body" ] - }] - }, - - "VariablesRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "Retrieves all child variables for the given variable reference.\nAn optional filter can be used to limit the fetched children to either named or indexed children.", - "properties": { - "command": { - "type": "string", - "enum": [ "variables" ] - }, - "arguments": { - "$ref": "#/definitions/VariablesArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "VariablesArguments": { - "type": "object", - "description": "Arguments for 'variables' request.", - "properties": { - "variablesReference": { - "type": "integer", - "description": "The Variable reference." - }, - "filter": { - "type": "string", - "enum": [ "indexed", "named" ], - "description": "Optional filter to limit the child variables to either named or indexed. If ommited, both types are fetched." - }, - "start": { - "type": "integer", - "description": "The index of the first variable to return; if omitted children start at 0." - }, - "count": { - "type": "integer", - "description": "The number of variables to return. If count is missing or 0, all variables are returned." - }, - "format": { - "$ref": "#/definitions/ValueFormat", - "description": "Specifies details on how to format the Variable values." - } - }, - "required": [ "variablesReference" ] - }, - "VariablesResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'variables' request.", - "properties": { - "body": { - "type": "object", - "properties": { - "variables": { - "type": "array", - "items": { - "$ref": "#/definitions/Variable" - }, - "description": "All (or a range) of variables for the given variable reference." - } - }, - "required": [ "variables" ] - } - }, - "required": [ "body" ] - }] - }, - - "SetVariableRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "Set the variable with the given name in the variable container to a new value.", - "properties": { - "command": { - "type": "string", - "enum": [ "setVariable" ] - }, - "arguments": { - "$ref": "#/definitions/SetVariableArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "SetVariableArguments": { - "type": "object", - "description": "Arguments for 'setVariable' request.", - "properties": { - "variablesReference": { - "type": "integer", - "description": "The reference of the variable container." - }, - "name": { - "type": "string", - "description": "The name of the variable." - }, - "value": { - "type": "string", - "description": "The value of the variable." - }, - "format": { - "$ref": "#/definitions/ValueFormat", - "description": "Specifies details on how to format the response value." - } - }, - "required": [ "variablesReference", "name", "value" ] - }, - "SetVariableResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'setVariable' request.", - "properties": { - "body": { - "type": "object", - "properties": { - "value": { - "type": "string", - "description": "The new value of the variable." - }, - "type": { - "type": "string", - "description": "The type of the new value. Typically shown in the UI when hovering over the value." - }, - "variablesReference": { - "type": "number", - "description": "If variablesReference is > 0, the new value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest." - }, - "namedVariables": { - "type": "number", - "description": "The number of named child variables.\nThe client can use this optional information to present the variables in a paged UI and fetch them in chunks." - }, - "indexedVariables": { - "type": "number", - "description": "The number of indexed child variables.\nThe client can use this optional information to present the variables in a paged UI and fetch them in chunks." - } - }, - "required": [ "value" ] - } - }, - "required": [ "body" ] - }] - }, - - "SourceRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The request retrieves the source code for a given source reference.", - "properties": { - "command": { - "type": "string", - "enum": [ "source" ] - }, - "arguments": { - "$ref": "#/definitions/SourceArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "SourceArguments": { - "type": "object", - "description": "Arguments for 'source' request.", - "properties": { - "source": { - "$ref": "#/definitions/Source", - "description": "Specifies the source content to load. Either source.path or source.sourceReference must be specified." - }, - "sourceReference": { - "type": "integer", - "description": "The reference to the source. This is the same as source.sourceReference. This is provided for backward compatibility since old backends do not understand the 'source' attribute." - } - }, - "required": [ "sourceReference" ] - }, - "SourceResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'source' request.", - "properties": { - "body": { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "Content of the source reference." - }, - "mimeType": { - "type": "string", - "description": "Optional content type (mime type) of the source." - } - }, - "required": [ "content" ] - } - }, - "required": [ "body" ] - }] - }, - - "ThreadsRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The request retrieves a list of all threads.", - "properties": { - "command": { - "type": "string", - "enum": [ "threads" ] - } - }, - "required": [ "command" ] - }] - }, - "ThreadsResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'threads' request.", - "properties": { - "body": { - "type": "object", - "properties": { - "threads": { - "type": "array", - "items": { - "$ref": "#/definitions/Thread" - }, - "description": "All threads." - } - }, - "required": [ "threads" ] - } - }, - "required": [ "body" ] - }] - }, - - "TerminateThreadsRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "The request terminates the threads with the given ids.", - "properties": { - "command": { - "type": "string", - "enum": [ "terminateThreads" ] - }, - "arguments": { - "$ref": "#/definitions/TerminateThreadsArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "TerminateThreadsArguments": { - "type": "object", - "description": "Arguments for 'terminateThreads' request.", - "properties": { - "threadIds": { - "type": "array", - "items": { - "type": "integer" - }, - "description": "Ids of threads to be terminated." - } - } - }, - "TerminateThreadsResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'terminateThreads' request. This is just an acknowledgement, so no body field is required." - }] - }, - - "ModulesRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "Modules can be retrieved from the debug adapter with the ModulesRequest which can either return all modules or a range of modules to support paging.", - "properties": { - "command": { - "type": "string", - "enum": [ "modules" ] - }, - "arguments": { - "$ref": "#/definitions/ModulesArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "ModulesArguments": { - "type": "object", - "description": "Arguments for 'modules' request.", - "properties": { - "startModule": { - "type": "integer", - "description": "The index of the first module to return; if omitted modules start at 0." - }, - "moduleCount": { - "type": "integer", - "description": "The number of modules to return. If moduleCount is not specified or 0, all modules are returned." - } - } - }, - "ModulesResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'modules' request.", - "properties": { - "body": { - "type": "object", - "properties": { - "modules": { - "type": "array", - "items": { - "$ref": "#/definitions/Module" - }, - "description": "All modules or range of modules." - }, - "totalModules": { - "type": "integer", - "description": "The total number of modules available." - } - }, - "required": [ "modules" ] - } - }, - "required": [ "body" ] - }] - }, - - "LoadedSourcesRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "Retrieves the set of all sources currently loaded by the debugged process.", - "properties": { - "command": { - "type": "string", - "enum": [ "loadedSources" ] - }, - "arguments": { - "$ref": "#/definitions/LoadedSourcesArguments" - } - }, - "required": [ "command" ] - }] - }, - "LoadedSourcesArguments": { - "type": "object", - "description": "Arguments for 'loadedSources' request." - }, - "LoadedSourcesResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'loadedSources' request.", - "properties": { - "body": { - "type": "object", - "properties": { - "sources": { - "type": "array", - "items": { - "$ref": "#/definitions/Source" - }, - "description": "Set of loaded sources." - } - }, - "required": [ "sources" ] - } - }, - "required": [ "body" ] - }] - }, - - "EvaluateRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "Evaluates the given expression in the context of the top most stack frame.\nThe expression has access to any variables and arguments that are in scope.", - "properties": { - "command": { - "type": "string", - "enum": [ "evaluate" ] - }, - "arguments": { - "$ref": "#/definitions/EvaluateArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "EvaluateArguments": { - "type": "object", - "description": "Arguments for 'evaluate' request.", - "properties": { - "expression": { - "type": "string", - "description": "The expression to evaluate." - }, - "frameId": { - "type": "integer", - "description": "Evaluate the expression in the scope of this stack frame. If not specified, the expression is evaluated in the global scope." - }, - "context": { - "type": "string", - "_enum": [ "watch", "repl", "hover" ], - "enumDescriptions": [ - "evaluate is run in a watch.", - "evaluate is run from REPL console.", - "evaluate is run from a data hover." - ], - "description": "The context in which the evaluate request is run." - }, - "format": { - "$ref": "#/definitions/ValueFormat", - "description": "Specifies details on how to format the Evaluate result." - } - }, - "required": [ "expression" ] - }, - "EvaluateResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'evaluate' request.", - "properties": { - "body": { - "type": "object", - "properties": { - "result": { - "type": "string", - "description": "The result of the evaluate request." - }, - "type": { - "type": "string", - "description": "The optional type of the evaluate result." - }, - "presentationHint": { - "$ref": "#/definitions/VariablePresentationHint", - "description": "Properties of a evaluate result that can be used to determine how to render the result in the UI." - }, - "variablesReference": { - "type": "number", - "description": "If variablesReference is > 0, the evaluate result is structured and its children can be retrieved by passing variablesReference to the VariablesRequest." - }, - "namedVariables": { - "type": "number", - "description": "The number of named child variables.\nThe client can use this optional information to present the variables in a paged UI and fetch them in chunks." - }, - "indexedVariables": { - "type": "number", - "description": "The number of indexed child variables.\nThe client can use this optional information to present the variables in a paged UI and fetch them in chunks." - } - }, - "required": [ "result", "variablesReference" ] - } - }, - "required": [ "body" ] - }] - }, - - "SetExpressionRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "Evaluates the given 'value' expression and assigns it to the 'expression' which must be a modifiable l-value.\nThe expressions have access to any variables and arguments that are in scope of the specified frame.", - "properties": { - "command": { - "type": "string", - "enum": [ "setExpression" ] - }, - "arguments": { - "$ref": "#/definitions/SetExpressionArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "SetExpressionArguments": { - "type": "object", - "description": "Arguments for 'setExpression' request.", - "properties": { - "expression": { - "type": "string", - "description": "The l-value expression to assign to." - }, - "value": { - "type": "string", - "description": "The value expression to assign to the l-value expression." - }, - "frameId": { - "type": "integer", - "description": "Evaluate the expressions in the scope of this stack frame. If not specified, the expressions are evaluated in the global scope." - }, - "format": { - "$ref": "#/definitions/ValueFormat", - "description": "Specifies how the resulting value should be formatted." - } - }, - "required": [ "expression", "value" ] - }, - "SetExpressionResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'setExpression' request.", - "properties": { - "body": { - "type": "object", - "properties": { - "value": { - "type": "string", - "description": "The new value of the expression." - }, - "type": { - "type": "string", - "description": "The optional type of the value." - }, - "presentationHint": { - "$ref": "#/definitions/VariablePresentationHint", - "description": "Properties of a value that can be used to determine how to render the result in the UI." - }, - "variablesReference": { - "type": "number", - "description": "If variablesReference is > 0, the value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest." - }, - "namedVariables": { - "type": "number", - "description": "The number of named child variables.\nThe client can use this optional information to present the variables in a paged UI and fetch them in chunks." - }, - "indexedVariables": { - "type": "number", - "description": "The number of indexed child variables.\nThe client can use this optional information to present the variables in a paged UI and fetch them in chunks." - } - }, - "required": [ "value" ] - } - }, - "required": [ "body" ] - }] - }, - - "StepInTargetsRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "This request retrieves the possible stepIn targets for the specified stack frame.\nThese targets can be used in the 'stepIn' request.\nThe StepInTargets may only be called if the 'supportsStepInTargetsRequest' capability exists and is true.", - "properties": { - "command": { - "type": "string", - "enum": [ "stepInTargets" ] - }, - "arguments": { - "$ref": "#/definitions/StepInTargetsArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "StepInTargetsArguments": { - "type": "object", - "description": "Arguments for 'stepInTargets' request.", - "properties": { - "frameId": { - "type": "integer", - "description": "The stack frame for which to retrieve the possible stepIn targets." - } - }, - "required": [ "frameId" ] - }, - "StepInTargetsResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'stepInTargets' request.", - "properties": { - "body": { - "type": "object", - "properties": { - "targets": { - "type": "array", - "items": { - "$ref": "#/definitions/StepInTarget" - }, - "description": "The possible stepIn targets of the specified source location." - } - }, - "required": [ "targets" ] - } - }, - "required": [ "body" ] - }] - }, - - "GotoTargetsRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "This request retrieves the possible goto targets for the specified source location.\nThese targets can be used in the 'goto' request.\nThe GotoTargets request may only be called if the 'supportsGotoTargetsRequest' capability exists and is true.", - "properties": { - "command": { - "type": "string", - "enum": [ "gotoTargets" ] - }, - "arguments": { - "$ref": "#/definitions/GotoTargetsArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "GotoTargetsArguments": { - "type": "object", - "description": "Arguments for 'gotoTargets' request.", - "properties": { - "source": { - "$ref": "#/definitions/Source", - "description": "The source location for which the goto targets are determined." - }, - "line": { - "type": "integer", - "description": "The line location for which the goto targets are determined." - }, - "column": { - "type": "integer", - "description": "An optional column location for which the goto targets are determined." - } - }, - "required": [ "source", "line" ] - }, - "GotoTargetsResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'gotoTargets' request.", - "properties": { - "body": { - "type": "object", - "properties": { - "targets": { - "type": "array", - "items": { - "$ref": "#/definitions/GotoTarget" - }, - "description": "The possible goto targets of the specified location." - } - }, - "required": [ "targets" ] - } - }, - "required": [ "body" ] - }] - }, - - "CompletionsRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "Returns a list of possible completions for a given caret position and text.\nThe CompletionsRequest may only be called if the 'supportsCompletionsRequest' capability exists and is true.", - "properties": { - "command": { - "type": "string", - "enum": [ "completions" ] - }, - "arguments": { - "$ref": "#/definitions/CompletionsArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "CompletionsArguments": { - "type": "object", - "description": "Arguments for 'completions' request.", - "properties": { - "frameId": { - "type": "integer", - "description": "Returns completions in the scope of this stack frame. If not specified, the completions are returned for the global scope." - }, - "text": { - "type": "string", - "description": "One or more source lines. Typically this is the text a user has typed into the debug console before he asked for completion." - }, - "column": { - "type": "integer", - "description": "The character position for which to determine the completion proposals." - }, - "line": { - "type": "integer", - "description": "An optional line for which to determine the completion proposals. If missing the first line of the text is assumed." - } - }, - "required": [ "text", "column" ] - }, - "CompletionsResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'completions' request.", - "properties": { - "body": { - "type": "object", - "properties": { - "targets": { - "type": "array", - "items": { - "$ref": "#/definitions/CompletionItem" - }, - "description": "The possible completions for ." - } - }, - "required": [ "targets" ] - } - }, - "required": [ "body" ] - }] - }, - - "ExceptionInfoRequest": { - "allOf": [ { "$ref": "#/definitions/Request" }, { - "type": "object", - "description": "Retrieves the details of the exception that caused this event to be raised.", - "properties": { - "command": { - "type": "string", - "enum": [ "exceptionInfo" ] - }, - "arguments": { - "$ref": "#/definitions/ExceptionInfoArguments" - } - }, - "required": [ "command", "arguments" ] - }] - }, - "ExceptionInfoArguments": { - "type": "object", - "description": "Arguments for 'exceptionInfo' request.", - "properties": { - "threadId": { - "type": "integer", - "description": "Thread for which exception information should be retrieved." - } - }, - "required": [ "threadId" ] - }, - "ExceptionInfoResponse": { - "allOf": [ { "$ref": "#/definitions/Response" }, { - "type": "object", - "description": "Response to 'exceptionInfo' request.", - "properties": { - "body": { - "type": "object", - "properties": { - "exceptionId": { - "type": "string", - "description": "ID of the exception that was thrown." - }, - "description": { - "type": "string", - "description": "Descriptive text for the exception provided by the debug adapter." - }, - "breakMode": { - "$ref": "#/definitions/ExceptionBreakMode", - "description": "Mode that caused the exception notification to be raised." - }, - "details": { - "$ref": "#/definitions/ExceptionDetails", - "description": "Detailed information about the exception." - } - }, - "required": [ "exceptionId", "breakMode" ] - } - }, - "required": [ "body" ] - }] - }, - - "Capabilities": { - "type": "object", - "title": "Types", - "description": "Information about the capabilities of a debug adapter.", - "properties": { - "supportsConfigurationDoneRequest": { - "type": "boolean", - "description": "The debug adapter supports the 'configurationDone' request." - }, - "supportsFunctionBreakpoints": { - "type": "boolean", - "description": "The debug adapter supports function breakpoints." - }, - "supportsConditionalBreakpoints": { - "type": "boolean", - "description": "The debug adapter supports conditional breakpoints." - }, - "supportsHitConditionalBreakpoints": { - "type": "boolean", - "description": "The debug adapter supports breakpoints that break execution after a specified number of hits." - }, - "supportsEvaluateForHovers": { - "type": "boolean", - "description": "The debug adapter supports a (side effect free) evaluate request for data hovers." - }, - "exceptionBreakpointFilters": { - "type": "array", - "items": { - "$ref": "#/definitions/ExceptionBreakpointsFilter" - }, - "description": "Available filters or options for the setExceptionBreakpoints request." - }, - "supportsStepBack": { - "type": "boolean", - "description": "The debug adapter supports stepping back via the 'stepBack' and 'reverseContinue' requests." - }, - "supportsSetVariable": { - "type": "boolean", - "description": "The debug adapter supports setting a variable to a value." - }, - "supportsRestartFrame": { - "type": "boolean", - "description": "The debug adapter supports restarting a frame." - }, - "supportsGotoTargetsRequest": { - "type": "boolean", - "description": "The debug adapter supports the 'gotoTargets' request." - }, - "supportsStepInTargetsRequest": { - "type": "boolean", - "description": "The debug adapter supports the 'stepInTargets' request." - }, - "supportsCompletionsRequest": { - "type": "boolean", - "description": "The debug adapter supports the 'completions' request." - }, - "supportsModulesRequest": { - "type": "boolean", - "description": "The debug adapter supports the 'modules' request." - }, - "additionalModuleColumns": { - "type": "array", - "items": { - "$ref": "#/definitions/ColumnDescriptor" - }, - "description": "The set of additional module information exposed by the debug adapter." - }, - "supportedChecksumAlgorithms": { - "type": "array", - "items": { - "$ref": "#/definitions/ChecksumAlgorithm" - }, - "description": "Checksum algorithms supported by the debug adapter." - }, - "supportsRestartRequest": { - "type": "boolean", - "description": "The debug adapter supports the 'restart' request. In this case a client should not implement 'restart' by terminating and relaunching the adapter but by calling the RestartRequest." - }, - "supportsExceptionOptions": { - "type": "boolean", - "description": "The debug adapter supports 'exceptionOptions' on the setExceptionBreakpoints request." - }, - "supportsValueFormattingOptions": { - "type": "boolean", - "description": "The debug adapter supports a 'format' attribute on the stackTraceRequest, variablesRequest, and evaluateRequest." - }, - "supportsExceptionInfoRequest": { - "type": "boolean", - "description": "The debug adapter supports the 'exceptionInfo' request." - }, - "supportTerminateDebuggee": { - "type": "boolean", - "description": "The debug adapter supports the 'terminateDebuggee' attribute on the 'disconnect' request." - }, - "supportsDelayedStackTraceLoading": { - "type": "boolean", - "description": "The debug adapter supports the delayed loading of parts of the stack, which requires that both the 'startFrame' and 'levels' arguments and the 'totalFrames' result of the 'StackTrace' request are supported." - }, - "supportsLoadedSourcesRequest": { - "type": "boolean", - "description": "The debug adapter supports the 'loadedSources' request." - }, - "supportsLogPoints": { - "type": "boolean", - "description": "The debug adapter supports logpoints by interpreting the 'logMessage' attribute of the SourceBreakpoint." - }, - "supportsTerminateThreadsRequest": { - "type": "boolean", - "description": "The debug adapter supports the 'terminateThreads' request." - }, - "supportsSetExpression": { - "type": "boolean", - "description": "The debug adapter supports the 'setExpression' request." - }, - "supportsTerminateRequest": { - "type": "boolean", - "description": "The debug adapter supports the 'terminate' request." - } - } - }, - - "ExceptionBreakpointsFilter": { - "type": "object", - "description": "An ExceptionBreakpointsFilter is shown in the UI as an option for configuring how exceptions are dealt with.", - "properties": { - "filter": { - "type": "string", - "description": "The internal ID of the filter. This value is passed to the setExceptionBreakpoints request." - }, - "label": { - "type": "string", - "description": "The name of the filter. This will be shown in the UI." - }, - "default": { - "type": "boolean", - "description": "Initial value of the filter. If not specified a value 'false' is assumed." - } - }, - "required": [ "filter", "label" ] - }, - - "Message": { - "type": "object", - "description": "A structured message object. Used to return errors from requests.", - "properties": { - "id": { - "type": "integer", - "description": "Unique identifier for the message." - }, - "format": { - "type": "string", - "description": "A format string for the message. Embedded variables have the form '{name}'.\nIf variable name starts with an underscore character, the variable does not contain user data (PII) and can be safely used for telemetry purposes." - }, - "variables": { - "type": "object", - "description": "An object used as a dictionary for looking up the variables in the format string.", - "additionalProperties": { - "type": "string", - "description": "Values must be strings." - } - }, - "sendTelemetry": { - "type": "boolean", - "description": "If true send to telemetry." - }, - "showUser": { - "type": "boolean", - "description": "If true show user." - }, - "url": { - "type": "string", - "description": "An optional url where additional information about this message can be found." - }, - "urlLabel": { - "type": "string", - "description": "An optional label that is presented to the user as the UI for opening the url." - } - }, - "required": [ "id", "format" ] - }, - - "Module": { - "type": "object", - "description": "A Module object represents a row in the modules view.\nTwo attributes are mandatory: an id identifies a module in the modules view and is used in a ModuleEvent for identifying a module for adding, updating or deleting.\nThe name is used to minimally render the module in the UI.\n\nAdditional attributes can be added to the module. They will show up in the module View if they have a corresponding ColumnDescriptor.\n\nTo avoid an unnecessary proliferation of additional attributes with similar semantics but different names\nwe recommend to re-use attributes from the 'recommended' list below first, and only introduce new attributes if nothing appropriate could be found.", - "properties": { - "id": { - "type": ["integer", "string"], - "description": "Unique identifier for the module." - }, - "name": { - "type": "string", - "description": "A name of the module." - }, - "path": { - "type": "string", - "description": "optional but recommended attributes.\nalways try to use these first before introducing additional attributes.\n\nLogical full path to the module. The exact definition is implementation defined, but usually this would be a full path to the on-disk file for the module." - }, - "isOptimized": { - "type": "boolean", - "description": "True if the module is optimized." - }, - "isUserCode": { - "type": "boolean", - "description": "True if the module is considered 'user code' by a debugger that supports 'Just My Code'." - }, - "version": { - "type": "string", - "description": "Version of Module." - }, - "symbolStatus": { - "type": "string", - "description": "User understandable description of if symbols were found for the module (ex: 'Symbols Loaded', 'Symbols not found', etc." - }, - "symbolFilePath": { - "type": "string", - "description": "Logical full path to the symbol file. The exact definition is implementation defined." - }, - "dateTimeStamp": { - "type": "string", - "description": "Module created or modified." - }, - "addressRange": { - "type": "string", - "description": "Address range covered by this module." - } - }, - "required": [ "id", "name" ] - }, - - "ColumnDescriptor": { - "type": "object", - "description": "A ColumnDescriptor specifies what module attribute to show in a column of the ModulesView, how to format it, and what the column's label should be.\nIt is only used if the underlying UI actually supports this level of customization.", - "properties": { - "attributeName": { - "type": "string", - "description": "Name of the attribute rendered in this column." - }, - "label": { - "type": "string", - "description": "Header UI label of column." - }, - "format": { - "type": "string", - "description": "Format to use for the rendered values in this column. TBD how the format strings looks like." - }, - "type": { - "type": "string", - "enum": [ "string", "number", "boolean", "unixTimestampUTC" ], - "description": "Datatype of values in this column. Defaults to 'string' if not specified." - }, - "width": { - "type": "integer", - "description": "Width of this column in characters (hint only)." - } - }, - "required": [ "attributeName", "label"] - }, - - "ModulesViewDescriptor": { - "type": "object", - "description": "The ModulesViewDescriptor is the container for all declarative configuration options of a ModuleView.\nFor now it only specifies the columns to be shown in the modules view.", - "properties": { - "columns": { - "type": "array", - "items": { - "$ref": "#/definitions/ColumnDescriptor" - } - } - }, - "required": [ "columns" ] - }, - - "Thread": { - "type": "object", - "description": "A Thread", - "properties": { - "id": { - "type": "integer", - "description": "Unique identifier for the thread." - }, - "name": { - "type": "string", - "description": "A name of the thread." - } - }, - "required": [ "id", "name" ] - }, - - "Source": { - "type": "object", - "description": "A Source is a descriptor for source code. It is returned from the debug adapter as part of a StackFrame and it is used by clients when specifying breakpoints.", - "properties": { - "name": { - "type": "string", - "description": "The short name of the source. Every source returned from the debug adapter has a name. When sending a source to the debug adapter this name is optional." - }, - "path": { - "type": "string", - "description": "The path of the source to be shown in the UI. It is only used to locate and load the content of the source if no sourceReference is specified (or its value is 0)." - }, - "sourceReference": { - "type": "number", - "description": "If sourceReference > 0 the contents of the source must be retrieved through the SourceRequest (even if a path is specified). A sourceReference is only valid for a session, so it must not be used to persist a source." - }, - "presentationHint": { - "type": "string", - "description": "An optional hint for how to present the source in the UI. A value of 'deemphasize' can be used to indicate that the source is not available or that it is skipped on stepping.", - "enum": [ "normal", "emphasize", "deemphasize" ] - }, - "origin": { - "type": "string", - "description": "The (optional) origin of this source: possible values 'internal module', 'inlined content from source map', etc." - }, - "sources": { - "type": "array", - "items": { - "$ref": "#/definitions/Source" - }, - "description": "An optional list of sources that are related to this source. These may be the source that generated this source." - }, - "adapterData": { - "type": [ "array", "boolean", "integer", "null", "number", "object", "string" ], - "description": "Optional data that a debug adapter might want to loop through the client. The client should leave the data intact and persist it across sessions. The client should not interpret the data." - }, - "checksums": { - "type": "array", - "items": { - "$ref": "#/definitions/Checksum" - }, - "description": "The checksums associated with this file." - } - } - }, - - "StackFrame": { - "type": "object", - "description": "A Stackframe contains the source location.", - "properties": { - "id": { - "type": "integer", - "description": "An identifier for the stack frame. It must be unique across all threads. This id can be used to retrieve the scopes of the frame with the 'scopesRequest' or to restart the execution of a stackframe." - }, - "name": { - "type": "string", - "description": "The name of the stack frame, typically a method name." - }, - "source": { - "$ref": "#/definitions/Source", - "description": "The optional source of the frame." - }, - "line": { - "type": "integer", - "description": "The line within the file of the frame. If source is null or doesn't exist, line is 0 and must be ignored." - }, - "column": { - "type": "integer", - "description": "The column within the line. If source is null or doesn't exist, column is 0 and must be ignored." - }, - "endLine": { - "type": "integer", - "description": "An optional end line of the range covered by the stack frame." - }, - "endColumn": { - "type": "integer", - "description": "An optional end column of the range covered by the stack frame." - }, - "moduleId": { - "type": ["integer", "string"], - "description": "The module associated with this frame, if any." - }, - "presentationHint": { - "type": "string", - "enum": [ "normal", "label", "subtle" ], - "description": "An optional hint for how to present this frame in the UI. A value of 'label' can be used to indicate that the frame is an artificial frame that is used as a visual label or separator. A value of 'subtle' can be used to change the appearance of a frame in a 'subtle' way." - } - }, - "required": [ "id", "name", "line", "column" ] - }, - - "Scope": { - "type": "object", - "description": "A Scope is a named container for variables. Optionally a scope can map to a source or a range within a source.", - "properties": { - "name": { - "type": "string", - "description": "Name of the scope such as 'Arguments', 'Locals'." - }, - "variablesReference": { - "type": "integer", - "description": "The variables of this scope can be retrieved by passing the value of variablesReference to the VariablesRequest." - }, - "namedVariables": { - "type": "integer", - "description": "The number of named variables in this scope.\nThe client can use this optional information to present the variables in a paged UI and fetch them in chunks." - }, - "indexedVariables": { - "type": "integer", - "description": "The number of indexed variables in this scope.\nThe client can use this optional information to present the variables in a paged UI and fetch them in chunks." - }, - "expensive": { - "type": "boolean", - "description": "If true, the number of variables in this scope is large or expensive to retrieve." - }, - "source": { - "$ref": "#/definitions/Source", - "description": "Optional source for this scope." - }, - "line": { - "type": "integer", - "description": "Optional start line of the range covered by this scope." - }, - "column": { - "type": "integer", - "description": "Optional start column of the range covered by this scope." - }, - "endLine": { - "type": "integer", - "description": "Optional end line of the range covered by this scope." - }, - "endColumn": { - "type": "integer", - "description": "Optional end column of the range covered by this scope." - } - }, - "required": [ "name", "variablesReference", "expensive" ] - }, - - "Variable": { - "type": "object", - "description": "A Variable is a name/value pair.\nOptionally a variable can have a 'type' that is shown if space permits or when hovering over the variable's name.\nAn optional 'kind' is used to render additional properties of the variable, e.g. different icons can be used to indicate that a variable is public or private.\nIf the value is structured (has children), a handle is provided to retrieve the children with the VariablesRequest.\nIf the number of named or indexed children is large, the numbers should be returned via the optional 'namedVariables' and 'indexedVariables' attributes.\nThe client can use this optional information to present the children in a paged UI and fetch them in chunks.", - "properties": { - "name": { - "type": "string", - "description": "The variable's name." - }, - "value": { - "type": "string", - "description": "The variable's value. This can be a multi-line text, e.g. for a function the body of a function." - }, - "type": { - "type": "string", - "description": "The type of the variable's value. Typically shown in the UI when hovering over the value." - }, - "presentationHint": { - "$ref": "#/definitions/VariablePresentationHint", - "description": "Properties of a variable that can be used to determine how to render the variable in the UI." - }, - "evaluateName": { - "type": "string", - "description": "Optional evaluatable name of this variable which can be passed to the 'EvaluateRequest' to fetch the variable's value." - }, - "variablesReference": { - "type": "integer", - "description": "If variablesReference is > 0, the variable is structured and its children can be retrieved by passing variablesReference to the VariablesRequest." - }, - "namedVariables": { - "type": "integer", - "description": "The number of named child variables.\nThe client can use this optional information to present the children in a paged UI and fetch them in chunks." - }, - "indexedVariables": { - "type": "integer", - "description": "The number of indexed child variables.\nThe client can use this optional information to present the children in a paged UI and fetch them in chunks." - } - }, - "required": [ "name", "value", "variablesReference" ] - }, - - "VariablePresentationHint": { - "type": "object", - "description": "Optional properties of a variable that can be used to determine how to render the variable in the UI.", - "properties": { - "kind": { - "description": "The kind of variable. Before introducing additional values, try to use the listed values.", - "type": "string", - "_enum": [ "property", "method", "class", "data", "event", "baseClass", "innerClass", "interface", "mostDerivedClass", "virtual" ], - "enumDescriptions": [ - "Indicates that the object is a property.", - "Indicates that the object is a method.", - "Indicates that the object is a class.", - "Indicates that the object is data.", - "Indicates that the object is an event.", - "Indicates that the object is a base class.", - "Indicates that the object is an inner class.", - "Indicates that the object is an interface.", - "Indicates that the object is the most derived class.", - "Indicates that the object is virtual, that means it is a synthetic object introduced by the adapter for rendering purposes, e.g. an index range for large arrays." - ] - }, - "attributes": { - "description": "Set of attributes represented as an array of strings. Before introducing additional values, try to use the listed values.", - "type": "array", - "items": { - "type": "string", - "_enum": [ "static", "constant", "readOnly", "rawString", "hasObjectId", "canHaveObjectId", "hasSideEffects" ], - "enumDescriptions": [ - "Indicates that the object is static.", - "Indicates that the object is a constant.", - "Indicates that the object is read only.", - "Indicates that the object is a raw string.", - "Indicates that the object can have an Object ID created for it.", - "Indicates that the object has an Object ID associated with it.", - "Indicates that the evaluation had side effects." - ] - } - }, - "visibility": { - "description": "Visibility of variable. Before introducing additional values, try to use the listed values.", - "type": "string", - "_enum": [ "public", "private", "protected", "internal", "final" ] - } - } - }, - - "SourceBreakpoint": { - "type": "object", - "description": "Properties of a breakpoint or logpoint passed to the setBreakpoints request.", - "properties": { - "line": { - "type": "integer", - "description": "The source line of the breakpoint or logpoint." - }, - "column": { - "type": "integer", - "description": "An optional source column of the breakpoint." - }, - "condition": { - "type": "string", - "description": "An optional expression for conditional breakpoints." - }, - "hitCondition": { - "type": "string", - "description": "An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed." - }, - "logMessage": { - "type": "string", - "description": "If this attribute exists and is non-empty, the backend must not 'break' (stop) but log the message instead. Expressions within {} are interpolated." - } - }, - "required": [ "line" ] - }, - - "FunctionBreakpoint": { - "type": "object", - "description": "Properties of a breakpoint passed to the setFunctionBreakpoints request.", - "properties": { - "name": { - "type": "string", - "description": "The name of the function." - }, - "condition": { - "type": "string", - "description": "An optional expression for conditional breakpoints." - }, - "hitCondition": { - "type": "string", - "description": "An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed." - } - }, - "required": [ "name" ] - }, - - "Breakpoint": { - "type": "object", - "description": "Information about a Breakpoint created in setBreakpoints or setFunctionBreakpoints.", - "properties": { - "id": { - "type": "integer", - "description": "An optional unique identifier for the breakpoint." - }, - "verified": { - "type": "boolean", - "description": "If true breakpoint could be set (but not necessarily at the desired location)." - }, - "message": { - "type": "string", - "description": "An optional message about the state of the breakpoint. This is shown to the user and can be used to explain why a breakpoint could not be verified." - }, - "source": { - "$ref": "#/definitions/Source", - "description": "The source where the breakpoint is located." - }, - "line": { - "type": "integer", - "description": "The start line of the actual range covered by the breakpoint." - }, - "column": { - "type": "integer", - "description": "An optional start column of the actual range covered by the breakpoint." - }, - "endLine": { - "type": "integer", - "description": "An optional end line of the actual range covered by the breakpoint." - }, - "endColumn": { - "type": "integer", - "description": "An optional end column of the actual range covered by the breakpoint. If no end line is given, then the end column is assumed to be in the start line." - } - }, - "required": [ "verified" ] - }, - - "StepInTarget": { - "type": "object", - "description": "A StepInTarget can be used in the 'stepIn' request and determines into which single target the stepIn request should step.", - "properties": { - "id": { - "type": "integer", - "description": "Unique identifier for a stepIn target." - }, - "label": { - "type": "string", - "description": "The name of the stepIn target (shown in the UI)." - } - }, - "required": [ "id", "label" ] - }, - - "GotoTarget": { - "type": "object", - "description": "A GotoTarget describes a code location that can be used as a target in the 'goto' request.\nThe possible goto targets can be determined via the 'gotoTargets' request.", - "properties": { - "id": { - "type": "integer", - "description": "Unique identifier for a goto target. This is used in the goto request." - }, - "label": { - "type": "string", - "description": "The name of the goto target (shown in the UI)." - }, - "line": { - "type": "integer", - "description": "The line of the goto target." - }, - "column": { - "type": "integer", - "description": "An optional column of the goto target." - }, - "endLine": { - "type": "integer", - "description": "An optional end line of the range covered by the goto target." - }, - "endColumn": { - "type": "integer", - "description": "An optional end column of the range covered by the goto target." - } - }, - "required": [ "id", "label", "line" ] - }, - - "CompletionItem": { - "type": "object", - "description": "CompletionItems are the suggestions returned from the CompletionsRequest.", - "properties": { - "label": { - "type": "string", - "description": "The label of this completion item. By default this is also the text that is inserted when selecting this completion." - }, - "text": { - "type": "string", - "description": "If text is not falsy then it is inserted instead of the label." - }, - "type": { - "$ref": "#/definitions/CompletionItemType", - "description": "The item's type. Typically the client uses this information to render the item in the UI with an icon." - }, - "start": { - "type": "integer", - "description": "This value determines the location (in the CompletionsRequest's 'text' attribute) where the completion text is added.\nIf missing the text is added at the location specified by the CompletionsRequest's 'column' attribute." - }, - "length": { - "type": "integer", - "description": "This value determines how many characters are overwritten by the completion text.\nIf missing the value 0 is assumed which results in the completion text being inserted." - } - }, - "required": [ "label" ] - }, - - "CompletionItemType": { - "type": "string", - "description": "Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them.", - "enum": [ "method", "function", "constructor", "field", "variable", "class", "interface", "module", "property", "unit", "value", "enum", "keyword", "snippet", "text", "color", "file", "reference", "customcolor" ] - }, - - "ChecksumAlgorithm": { - "type": "string", - "description": "Names of checksum algorithms that may be supported by a debug adapter.", - "enum": [ "MD5", "SHA1", "SHA256", "timestamp" ] - }, - - "Checksum": { - "type": "object", - "description": "The checksum of an item calculated by the specified algorithm.", - "properties": { - "algorithm": { - "$ref": "#/definitions/ChecksumAlgorithm", - "description": "The algorithm used to calculate this checksum." - }, - "checksum": { - "type": "string", - "description": "Value of the checksum." - } - }, - "required": [ "algorithm", "checksum" ] - }, - - "ValueFormat": { - "type": "object", - "description": "Provides formatting information for a value.", - "properties": { - "hex": { - "type": "boolean", - "description": "Display the value in hex." - } - } - }, - - "StackFrameFormat": { - "allOf": [ { "$ref": "#/definitions/ValueFormat" }, { - "type": "object", - "description": "Provides formatting information for a stack frame.", - "properties": { - "parameters": { - "type": "boolean", - "description": "Displays parameters for the stack frame." - }, - "parameterTypes": { - "type": "boolean", - "description": "Displays the types of parameters for the stack frame." - }, - "parameterNames": { - "type": "boolean", - "description": "Displays the names of parameters for the stack frame." - }, - "parameterValues": { - "type": "boolean", - "description": "Displays the values of parameters for the stack frame." - }, - "line": { - "type": "boolean", - "description": "Displays the line number of the stack frame." - }, - "module": { - "type": "boolean", - "description": "Displays the module of the stack frame." - }, - "includeAll": { - "type": "boolean", - "description": "Includes all stack frames, including those the debug adapter might otherwise hide." - } - } - }] - }, - - "ExceptionOptions": { - "type": "object", - "description": "An ExceptionOptions assigns configuration options to a set of exceptions.", - "properties": { - "path": { - "type": "array", - "items": { - "$ref": "#/definitions/ExceptionPathSegment" - }, - "description": "A path that selects a single or multiple exceptions in a tree. If 'path' is missing, the whole tree is selected. By convention the first segment of the path is a category that is used to group exceptions in the UI." - }, - "breakMode": { - "$ref": "#/definitions/ExceptionBreakMode", - "description": "Condition when a thrown exception should result in a break." - } - }, - "required": [ "breakMode" ] - }, - - "ExceptionBreakMode": { - "type": "string", - "description": "This enumeration defines all possible conditions when a thrown exception should result in a break.\nnever: never breaks,\nalways: always breaks,\nunhandled: breaks when excpetion unhandled,\nuserUnhandled: breaks if the exception is not handled by user code.", - "enum": [ "never", "always", "unhandled", "userUnhandled" ] - }, - - "ExceptionPathSegment": { - "type": "object", - "description": "An ExceptionPathSegment represents a segment in a path that is used to match leafs or nodes in a tree of exceptions. If a segment consists of more than one name, it matches the names provided if 'negate' is false or missing or it matches anything except the names provided if 'negate' is true.", - "properties": { - "negate": { - "type": "boolean", - "description": "If false or missing this segment matches the names provided, otherwise it matches anything except the names provided." - }, - "names": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Depending on the value of 'negate' the names that should match or not match." - } - }, - "required": [ "names" ] - }, - - "ExceptionDetails": { - "type": "object", - "description": "Detailed information about an exception that has occurred.", - "properties": { - "message": { - "type": "string", - "description": "Message contained in the exception." - }, - "typeName": { - "type": "string", - "description": "Short type name of the exception object." - }, - "fullTypeName": { - "type": "string", - "description": "Fully-qualified type name of the exception object." - }, - "evaluateName": { - "type": "string", - "description": "Optional expression that can be evaluated in the current scope to obtain the exception object." - }, - "stackTrace": { - "type": "string", - "description": "Stack trace at the time the exception was thrown." - }, - "innerException": { - "type": "array", - "items": { - "$ref": "#/definitions/ExceptionDetails" - }, - "description": "Details of the exception contained by this exception, if any." - } - } - } - - } -} diff --git a/debugger_protocol/schema/file.py b/debugger_protocol/schema/file.py deleted file mode 100644 index 34a0fa86..00000000 --- a/debugger_protocol/schema/file.py +++ /dev/null @@ -1,15 +0,0 @@ - - -class SchemaFileError(Exception): - """A schema-file-related operation failed.""" - - -def read_schema(filename, *, _open=open): - """Return the data (bytes) in the given schema file.""" - try: - schemafile = _open(filename, 'rb') - except FileNotFoundError: - raise SchemaFileError( - 'schema file {!r} not found'.format(filename)) - with schemafile: - return schemafile.read() diff --git a/debugger_protocol/schema/metadata.py b/debugger_protocol/schema/metadata.py deleted file mode 100644 index d32d8972..00000000 --- a/debugger_protocol/schema/metadata.py +++ /dev/null @@ -1,118 +0,0 @@ -from collections import namedtuple -from datetime import datetime -import os.path -from textwrap import dedent - -from ._util import github_url_replace_ref - - -class MetadataError(Exception): - """A metadata-related operation failed.""" - - -def open_metadata(schemafile, mode='r', *, _open=open): - """Return a file object for the metadata of the given schema file. - - Also return the metadata file's filename. - """ - from .vendored import METADATA # Here due to a circular import. - filename = os.path.join(os.path.dirname(schemafile), - os.path.basename(METADATA)) - try: - return _open(filename, mode), filename - except FileNotFoundError: - raise MetadataError( - 'metadata file for {!r} not found'.format(schemafile)) - - -def read_metadata(schemafile, *, _open=open): - """Return the metadata corresponding to the schema file. - - Also return the path to the metadata file. - """ - metafile, filename = open_metadata(schemafile, _open=_open) - with metafile: - data = metafile.read() - - try: - meta = Metadata.parse(data) - except Exception as exc: - raise MetadataError( - 'metadata file {!r} not valid: {}'.format(filename, exc)) - - return meta, filename - - -class Metadata( - namedtuple('Metadata', 'upstream revision checksum downloaded')): - """Info about the local copy of the upstream schema file.""" - - TIMESTAMP = '%Y-%m-%d %H:%M:%S (UTC)' - - FORMAT = dedent("""\ - upstream: {} - revision: {} - checksum: {} - downloaded: {:%s} - """) % TIMESTAMP - - @classmethod - def parse(cls, data): - """Return an instance based on the given metadata string.""" - lines = data.splitlines() - - kwargs = {} - for line in lines: - line = line.strip() - if line.startswith('#'): - continue - if not line: - continue - field, _, value = line.partition(':') - kwargs[field] = value.strip() - self = cls(**kwargs) - return self - - def __new__(cls, upstream, revision, checksum, downloaded): - # coercion - upstream = str(upstream) if upstream else None - revision = str(revision) if revision else None - checksum = str(checksum) if checksum else None - if not downloaded: - downloaded = None - elif isinstance(downloaded, str): - downloaded = datetime.strptime(downloaded, cls.TIMESTAMP) - elif downloaded.tzinfo is not None: - downloaded -= downloaded.utcoffset() - - self = super().__new__(cls, upstream, revision, checksum, downloaded) - return self - - def __init__(self, *args, **kwargs): - # validation - - if not self.upstream: - raise ValueError('missing upstream URL') - # TODO ensure upstream is URL? - - if not self.revision: - raise ValueError('missing upstream revision') - # TODO ensure revision is a hash? - - if not self.checksum: - raise ValueError('missing checksum') - # TODO ensure checksum is a MD5 hash? - - if not self.downloaded: - raise ValueError('missing downloaded') - - @property - def url(self): - if self.upstream.startswith('https://github.com/'): - return github_url_replace_ref(self.upstream, self.revision) - else: - raise NotImplementedError - - def format(self): - """Return a string containing the formatted metadata.""" - return self.FORMAT.format(*self) diff --git a/debugger_protocol/schema/upstream.py b/debugger_protocol/schema/upstream.py deleted file mode 100644 index 6ca572c9..00000000 --- a/debugger_protocol/schema/upstream.py +++ /dev/null @@ -1,36 +0,0 @@ -from datetime import datetime -import io -import urllib.error - -from ._util import open_url, get_revision, get_checksum -from .file import SchemaFileError -from .metadata import Metadata - - -URL = 'https://github.com/Microsoft/vscode-debugadapter-node/raw/master/debugProtocol.json' # noqa - - -def download(source, infile, outfile, *, - _now=datetime.utcnow, _open_url=open_url): - """Return the corresponding metadata after downloading the schema file.""" - timestamp = _now() - revision = get_revision(source, _open_url=_open_url) - - data = infile.read() - checksum = get_checksum(data) - outfile.write(data) - - return Metadata(source, revision, checksum, timestamp) - - -def read(url, *, _open_url=open_url): - """Return (data, metadata) for the given upstream URL.""" - outfile = io.BytesIO() - try: - infile = _open_url(url) - except (FileNotFoundError, urllib.error.HTTPError): - # TODO: Ensure it's a 404 error? - raise SchemaFileError('schema file at {!r} not found'.format(url)) - with infile: - upstream = download(url, infile, outfile, _open_url=_open_url) - return outfile.getvalue(), upstream diff --git a/debugger_protocol/schema/vendored.py b/debugger_protocol/schema/vendored.py deleted file mode 100644 index 27c4cb27..00000000 --- a/debugger_protocol/schema/vendored.py +++ /dev/null @@ -1,69 +0,0 @@ -import os.path - -from . import DATA_DIR, upstream -from ._util import open_url, get_checksum -from .file import SchemaFileError, read_schema -from .metadata import MetadataError, read_metadata - - -FILENAME = os.path.join(DATA_DIR, 'debugProtocol.json') -METADATA = os.path.join(DATA_DIR, 'UPSTREAM') - - -class SchemaFileMismatchError(SchemaFileError, MetadataError): - """The schema file does not match expectations.""" - - @classmethod - def _build_message(cls, filename, actual, expected, upstream): - if upstream: - msg = ('local schema file {!r} does not match upstream {!r}' - ).format(filename, expected.upstream) - else: - msg = ('schema file {!r} does not match metadata file' - ).format(filename) - - for field in actual._fields: - value = getattr(actual, field) - other = getattr(expected, field) - if value != other: - msg += (' ({} mismatch: {!r} != {!r})' - ).format(field, value, other) - break - - return msg - - def __init__(self, filename, actual, expected, *, upstream=False): - super().__init__( - self._build_message(filename, actual, expected, upstream)) - self.filename = filename - self.actual = actual - self.expected = expected - self.upstream = upstream - - -def check_local(filename, *, _open=open): - """Ensure that the local schema file matches the local metadata file.""" - # Get the vendored metadata and data. - meta, _ = read_metadata(filename, _open=_open) - data = read_schema(filename, _open=_open) - - # Only worry about the checksum matching. - actual = meta._replace( - checksum=get_checksum(data)) - if actual != meta: - raise SchemaFileMismatchError(filename, actual, meta) - - -def check_upstream(filename, url=None, *, _open=open, _open_url=open_url): - """Ensure that the local metadata file matches the upstream schema file.""" - # Get the vendored and upstream metadata. - meta, _ = read_metadata(filename, _open=_open) - if url is None: - url = meta.upstream - _, upmeta = upstream.read(url, _open_url=_open_url) - - # Make sure the revision and checksum match. - if meta.revision != upmeta.revision: - raise SchemaFileMismatchError(filename, meta, upmeta, upstream=True) - if meta.checksum != upmeta.checksum: - raise SchemaFileMismatchError(filename, meta, upmeta, upstream=True) diff --git a/pytest.ini b/pytest.ini index 7951f2b4..bb09027a 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] -testpaths=pytests +testpaths=tests timeout=15 timeout_method=thread diff --git a/pytests/__init__.py b/pytests/__init__.py deleted file mode 100644 index df0fd872..00000000 --- a/pytests/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See LICENSE in the project root -# for license information. - -__doc__ = """pytest-based ptvsd tests.""" - -import colorama -import pytest - -# This is only imported to ensure that the module is actually installed and the -# timeout setting in pytest.ini is active, since otherwise most timeline-based -# tests will hang indefinitely. -import pytest_timeout # noqa - - -colorama.init() -pytest.register_assert_rewrite('pytests.helpers') \ No newline at end of file diff --git a/pytests/func/testfiles/testpkgs/pkg1/__init__.py b/pytests/func/testfiles/testpkgs/pkg1/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pytests/helpers/__init__.py b/pytests/helpers/__init__.py deleted file mode 100644 index 2b15ffd1..00000000 --- a/pytests/helpers/__init__.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See LICENSE in the project root -# for license information. - -from __future__ import print_function, with_statement, absolute_import - -import os -import sys -import threading -import time -import traceback - - -if sys.version_info >= (3, 5): - clock = time.monotonic -else: - clock = time.clock - - -timestamp_zero = clock() - -def timestamp(): - return clock() - timestamp_zero - - -def dump_stacks(): - """Dump the stacks of all threads except the current thread""" - current_ident = threading.current_thread().ident - for thread_ident, frame in sys._current_frames().items(): - if thread_ident == current_ident: - continue - for t in threading.enumerate(): - if t.ident == thread_ident: - thread_name = t.name - thread_daemon = t.daemon - break - else: - thread_name = '' - print('Stack of %s (%s) in pid %s; daemon=%s' % (thread_name, thread_ident, os.getpid(), thread_daemon)) - print(''.join(traceback.format_stack(frame))) - - -def dump_stacks_in(secs): - """Invokes dump_stacks() on a background thread after waiting. - - Can be called from debugged code before the point after which it hangs, - to determine the cause of the hang while debugging a test. - """ - - def dumper(): - time.sleep(secs) - dump_stacks() - - thread = threading.Thread(target=dumper) - thread.daemon = True - thread.start() - - -from .printer import print diff --git a/pytests/helpers/webhelper.py b/pytests/helpers/webhelper.py deleted file mode 100644 index 428ee72b..00000000 --- a/pytests/helpers/webhelper.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See LICENSE in the project root -# for license information. - -import threading -import requests -import re -import socket -import time - -def get_web_string(path, obj): - r = requests.get(path) - content = r.text - if obj is not None: - obj.content = content - return content - - -def get_web_string_no_error(path, obj): - try: - return get_web_string(path, obj) - except Exception: - pass - - -re_link = r"(http(s|)\:\/\/[\w\.]*\:[0-9]{4,6}(\/|))" -def get_url_from_str(s): - matches = re.findall(re_link, s) - if matches and matches[0]and matches[0][0].strip(): - return matches[0][0] - return None - - -def get_web_content(link, web_result=None, timeout=1): - class WebResponse(object): - def __init__(self): - self.content = None - - def wait_for_response(self, timeout=1): - self._web_client_thread.join(timeout) - return self.content - - response = WebResponse() - response._web_client_thread = threading.Thread( - target=get_web_string_no_error, - args=(link, response), - name='test.webClient' - ) - response._web_client_thread.start() - return response - - -def wait_for_connection(port, interval=1, attempts=10): - count = 0 - while count < attempts: - count += 1 - try: - print('Waiting to connect to port: %s' % port) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect(('localhost', port)) - return - except socket.error: - pass - finally: - sock.close() - time.sleep(interval) diff --git a/tests/__init__.py b/tests/__init__.py index 0e3681eb..602b8cd1 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,46 +1,17 @@ -from __future__ import absolute_import +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See LICENSE in the project root +# for license information. -import os -import os.path -import sys -import unittest +__doc__ = """pytest-based ptvsd tests.""" -# Importing "ptvsd" here triggers the vendoring code before any vendored -# code ever gets imported. -import ptvsd # noqa -from ptvsd._vendored import list_all as vendored +import colorama +import pytest + +# This is only imported to ensure that the module is actually installed and the +# timeout setting in pytest.ini is active, since otherwise most timeline-based +# tests will hang indefinitely. +import pytest_timeout # noqa -TEST_ROOT = os.path.abspath(os.path.dirname(__file__)) # noqa -RESOURCES_ROOT = os.path.join(TEST_ROOT, 'resources') # noqa -PROJECT_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(ptvsd.__file__))) -VENDORED_ROOTS = vendored(resolve=True) # noqa - - -def skip_py2(decorated=None): - if sys.version_info[0] > 2: - return decorated - msg = 'not tested under Python 2' - if decorated is None: - raise unittest.SkipTest(msg) - else: - decorator = unittest.skip(msg) - return decorator(decorated) - - -if sys.version_info[0] == 2: - # Hack alert!!! - class SkippingTestSuite(unittest.TestSuite): - def __init__(self, tests=()): - if tests and type(tests[0]).__name__ == 'ModuleImportFailure': - _, exc, _ = sys.exc_info() - if isinstance(exc, unittest.SkipTest): - from unittest.loader import _make_failed_load_tests - suite = _make_failed_load_tests( - tests[0]._testMethodName, - exc, - type(self), - ) - tests = tuple(suite) - unittest.TestSuite.__init__(self, tests) - unittest.TestLoader.suiteClass = SkippingTestSuite +colorama.init() +pytest.register_assert_rewrite('tests.helpers') \ No newline at end of file diff --git a/tests/__main__.py b/tests/__main__.py deleted file mode 100644 index 74102b5f..00000000 --- a/tests/__main__.py +++ /dev/null @@ -1,231 +0,0 @@ -from __future__ import absolute_import - -import argparse -import os -import os.path -import subprocess -import sys -import unittest - -from . import TEST_ROOT, PROJECT_ROOT, VENDORED_ROOTS - - -def parse_cmdline(argv=None): - """Obtain command line arguments and setup the test run accordingly.""" - - parser = argparse.ArgumentParser( - description="Run tests associated to the PTVSD project.", - prog="tests", - usage="python -m %(prog)s OPTS", - add_help=False - ) - - # allow_abbrev was added in 3.5 - if sys.version_info >= (3, 5): - parser.allow_abbrev = False - - parser.add_argument( - "-c", - "--coverage", - help="Generate code coverage report.", - action="store_true" - ) - parser.add_argument( - "--full", - help="Do full suite of tests (disables prior --quick options).", - action="store_false", - dest="quick" - ) - parser.add_argument( - "-j", - "--junit-xml", - help="Output report is generated to JUnit-style XML file specified.", - type=str - ) - parser.add_argument( - "-l", - "--lint", - help="Run and report on Linter compliance.", - action="store_true" - ) - parser.add_argument( - "-L", - "--lint-only", - help="Run and report on Linter compliance only, do not perform tests.", - action="store_true" - ) - parser.add_argument( - "-n", - "--network", - help="Perform tests taht require network connectivity.", - action="store_true", - dest="network" - ) - parser.add_argument( - "--no-network", - help="Do not perform tests that require network connectivity.", - action="store_false", - dest="network" - ) - parser.add_argument( - "-q", - "--quick", - help="Only do the tests under test/ptvsd.", - action="store_true", - dest="quick" - ) - parser.add_argument( - "--quick-py2", - help=("Only do the tests under test/ptvsd, that are compatible " - "with Python 2.x."), - action="store_true" - ) - # these destinations have 2 switches, be explicit about the default - parser.set_defaults(quick=False) - parser.set_defaults(network=True) - config, passthrough_args = parser.parse_known_args(argv) - - return config, passthrough_args - - -def convert_argv(argv=None): - """Convert commandling args into unittest/linter/coverage input.""" - - config, passthru = parse_cmdline(argv) - - modules = set() - args = [] - help = False - - for arg in passthru: - # Unittest's main has only flags and positional args. - # So we don't worry about options with values. - if not arg.startswith('-'): - # It must be the name of a test, case, module, or file. - # We convert filenames to module names. For filenames - # we support specifying a test name by appending it to - # the filename with a ":" in between. - mod, _, test = arg.partition(':') - if mod.endswith(os.sep): - mod = mod.rsplit(os.sep, 1)[0] - mod = mod.rsplit('.py', 1)[0] - mod = mod.replace(os.sep, '.') - arg = mod if not test else mod + '.' + test - modules.add(mod) - elif arg in ('-h', '--help'): - help = True - args.append(arg) - - env = {} - if config.network: - env['HAS_NETWORK'] = '1' - # We make the "executable" a single arg because unittest.main() - # doesn't work if we split it into 3 parts. - cmd = [sys.executable + ' -m unittest'] - if not modules and not help: - # Do discovery. - quickroot = os.path.join(TEST_ROOT, 'ptvsd') - if config.quick: - start = quickroot - elif config.quick_py2 and sys.version_info[0] == 2: - start = quickroot - else: - start = PROJECT_ROOT - - cmd += [ - 'discover', - '--top-level-directory', PROJECT_ROOT, - '--start-directory', start, - ] - args = cmd + args - - return config, args, env - - -def is_cwd(path): - p1 = os.path.normcase(os.path.abspath(path)) - p2 = os.path.normcase(os.getcwd()) - return p1 == p2 - - -def fix_sys_path(): - pos = 1 if (not sys.path[0] or sys.path[0] == '.' or - is_cwd(sys.path[0])) else 0 - for projectroot in VENDORED_ROOTS: - sys.path.insert(pos, projectroot) - - -def check_lint(): - print('linting...') - args = [ - sys.executable, - '-m', 'flake8', - '--config', '.flake8', - PROJECT_ROOT, - ] - rc = subprocess.call(args) - if rc != 0: - print('...linting failed!') - sys.exit(rc) - print('...done') - - -def run_tests(argv, env, coverage, junit_xml): - print('running tests...') - if coverage: - omissions = [os.path.join(root, '*') for root in VENDORED_ROOTS] - # TODO: Drop the explicit pydevd omit once we move the subtree. - omissions.append(os.path.join('ptvsd', 'pydevd', '*')) - ver = 3 if sys.version_info < (3,) else 2 - omissions.append(os.path.join('ptvsd', 'reraise{}.py'.format(ver))) - args = [ - sys.executable, - '-m', 'coverage', - 'run', - # We use --source instead of "--include ptvsd/*". - '--source', 'ptvsd', - '--omit', ','.join(omissions), - '-m', 'unittest', - ] + argv[1:] - assert 'PYTHONPATH' not in env - env['PYTHONPATH'] = os.pathsep.join(VENDORED_ROOTS) - rc = subprocess.call(args, env=env) - if rc != 0: - print('...coverage failed!') - sys.exit(rc) - print('...done') - elif junit_xml: - from xmlrunner import XMLTestRunner # noqa - os.environ.update(env) - verbosity = 1 - if '-v' in argv or '--verbose' in argv: - verbosity = 2 - with open(junit_xml, 'wb') as output: - unittest.main( - testRunner=XMLTestRunner(output=output, verbosity=verbosity), - module=None, - argv=argv, - ) - else: - os.environ.update(env) - unittest.main(module=None, argv=argv) - - -if __name__ == '__main__': - config, argv, env = convert_argv() - fix_sys_path() - - if config.lint or config.lint_only: - check_lint() - - if not config.lint_only: - if '--start-directory' in argv: - start = argv[argv.index('--start-directory') + 1] - print('(will look for tests under {})'.format(start)) - - run_tests( - argv, - env, - config.coverage, - config.junit_xml - ) diff --git a/pytests/conftest.py b/tests/conftest.py similarity index 100% rename from pytests/conftest.py rename to tests/conftest.py diff --git a/tests/debugger_protocol/__init__.py b/tests/debugger_protocol/__init__.py deleted file mode 100644 index 65be4a92..00000000 --- a/tests/debugger_protocol/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .. import skip_py2 - - -# The code under the debugger_protocol package isn't used -# by the debugger (it's used by schema-related tools). So we don't need -# to support Python 2. -skip_py2() diff --git a/tests/debugger_protocol/arg/__init__.py b/tests/debugger_protocol/arg/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/debugger_protocol/arg/_common.py b/tests/debugger_protocol/arg/_common.py deleted file mode 100644 index 638d0bba..00000000 --- a/tests/debugger_protocol/arg/_common.py +++ /dev/null @@ -1,50 +0,0 @@ -from debugger_protocol.arg import ANY, FieldsNamespace, Field - - -FIELDS_BASIC = [ - Field('name'), - Field.START_OPTIONAL, - Field('value'), -] - -BASIC_FULL = { - 'name': 'spam', - 'value': 'eggs', -} - -BASIC_MIN = { - 'name': 'spam', -} - - -class Basic(FieldsNamespace): - FIELDS = FIELDS_BASIC - - -FIELDS_EXTENDED = [ - Field('name', datatype=str, optional=False), - Field('valid', datatype=bool, optional=True), - Field('id', datatype=int, optional=False), - Field('value', datatype=ANY, optional=True), - Field('x', datatype=Basic, optional=True), - Field('y', datatype={int, str}, optional=True), - Field('z', datatype=[Basic], optional=True), -] - -EXTENDED_FULL = { - 'name': 'spam', - 'valid': True, - 'id': 10, - 'value': None, - 'x': BASIC_FULL, - 'y': 11, - 'z': [ - BASIC_FULL, - BASIC_MIN, - ], -} - -EXTENDED_MIN = { - 'name': 'spam', - 'id': 10, -} diff --git a/tests/debugger_protocol/arg/test__datatype.py b/tests/debugger_protocol/arg/test__datatype.py deleted file mode 100644 index 326299d0..00000000 --- a/tests/debugger_protocol/arg/test__datatype.py +++ /dev/null @@ -1,365 +0,0 @@ -import itertools -import unittest - -from debugger_protocol.arg._common import ANY -from debugger_protocol.arg._datatype import FieldsNamespace -from debugger_protocol.arg._decl import Union, Array, Field, Fields -from debugger_protocol.arg._param import Parameter, DatatypeHandler, Arg - -from ._common import ( - BASIC_FULL, BASIC_MIN, Basic, - FIELDS_EXTENDED, EXTENDED_FULL, EXTENDED_MIN) - - -class FieldsNamespaceTests(unittest.TestCase): - - def test_traverse_noop(self): - fields = [ - Field('spam'), - Field('ham'), - Field('eggs'), - ] - - class Spam(FieldsNamespace): - FIELDS = Fields(*fields) - - calls = [] - op = (lambda dt: calls.append(dt) or dt) - transformed = Spam.traverse(op) - - self.assertIs(transformed, Spam) - self.assertIs(transformed.FIELDS, Spam.FIELDS) - for i, field in enumerate(Spam.FIELDS): - self.assertIs(field, fields[i]) - self.assertCountEqual(calls, [ - # Note that it did not recurse into the fields. - Field('spam'), - Field('ham'), - Field('eggs'), - ]) - - def test_traverse_unnormalized(self): - fields = [ - Field('spam'), - Field('ham'), - Field('eggs'), - ] - - class Spam(FieldsNamespace): - FIELDS = fields - - calls = [] - op = (lambda dt: calls.append(dt) or dt) - transformed = Spam.traverse(op) - - self.assertIs(transformed, Spam) - self.assertIsInstance(transformed.FIELDS, Fields) - for i, field in enumerate(Spam.FIELDS): - self.assertIs(field, fields[i]) - self.assertCountEqual(calls, [ - Field('spam'), - Field('ham'), - Field('eggs'), - ]) - - def test_traverse_changed(self): - class Spam(FieldsNamespace): - FIELDS = Fields( - Field('spam', ANY), - Field('eggs', None), - ) - - calls = [] - op = (lambda dt: calls.append(dt) or Field(dt.name, str)) - transformed = Spam.traverse(op) - - self.assertIs(transformed, Spam) - self.assertEqual(transformed.FIELDS, Fields( - Field('spam', str), - Field('eggs', str), - )) - self.assertEqual(calls, [ - Field('spam', ANY), - Field('eggs', None), - ]) - - def test_normalize_without_ops(self): - fieldlist = [ - Field('spam'), - Field('ham'), - Field('eggs'), - ] - fields = Fields(*fieldlist) - - class Spam(FieldsNamespace): - FIELDS = fields - - Spam.normalize() - - self.assertIs(Spam.FIELDS, fields) - for i, field in enumerate(Spam.FIELDS): - self.assertIs(field, fieldlist[i]) - - def test_normalize_unnormalized(self): - fieldlist = [ - Field('spam'), - Field('ham'), - Field('eggs'), - ] - - class Spam(FieldsNamespace): - FIELDS = fieldlist - - Spam.normalize() - - self.assertIsInstance(Spam.FIELDS, Fields) - for i, field in enumerate(Spam.FIELDS): - self.assertIs(field, fieldlist[i]) - - def test_normalize_with_ops_noop(self): - fieldlist = [ - Field('spam'), - Field('ham', int), - Field('eggs', Array(ANY)), - ] - fields = Fields(*fieldlist) - - class Spam(FieldsNamespace): - FIELDS = fields - - calls = [] - op1 = (lambda dt: calls.append((op1, dt)) or dt) - op2 = (lambda dt: calls.append((op2, dt)) or dt) - Spam.normalize(op1, op2) - - self.assertIs(Spam.FIELDS, fields) - for i, field in enumerate(Spam.FIELDS): - self.assertIs(field, fieldlist[i]) - self.maxDiff = None - self.assertEqual(calls, [ - (op1, fields), - (op1, Field('spam')), - (op1, str), - (op1, Field('ham', int)), - (op1, int), - (op1, Field('eggs', Array(ANY))), - (op1, Array(ANY)), - (op1, ANY), - - (op2, fields), - (op2, Field('spam')), - (op2, str), - (op2, Field('ham', int)), - (op2, int), - (op2, Field('eggs', Array(ANY))), - (op2, Array(ANY)), - (op2, ANY), - ]) - - def test_normalize_with_op_changed(self): - class Spam(FieldsNamespace): - FIELDS = Fields( - Field('spam', Array(ANY)), - ) - - op = (lambda dt: int if dt is ANY else dt) - Spam.normalize(op) - - self.assertEqual(Spam.FIELDS, Fields( - Field('spam', Array(int)), - )) - - def test_normalize_declarative(self): - class Spam(FieldsNamespace): - FIELDS = [ - Field('a'), - Field('b', bool), - Field.START_OPTIONAL, - Field('c', {int, str}), - Field('d', [int]), - Field('e', ANY), - Field('f', ''), - ] - - class Ham(FieldsNamespace): - FIELDS = [ - Field('w', Spam), - Field('x', int), - Field('y', frozenset({int, str})), - Field('z', (int,)), - ] - - class Eggs(FieldsNamespace): - FIELDS = [ - Field('b', [Ham]), - Field('x', [{str, ('',)}], optional=True), - Field('d', {Spam, ''}, optional=True), - ] - - Eggs.normalize() - - self.assertEqual(Spam.FIELDS, Fields( - Field('a'), - Field('b', bool), - Field('c', Union(int, str), optional=True), - Field('d', Array(int), optional=True), - Field('e', ANY, optional=True), - Field('f', Spam, optional=True), - )) - self.assertEqual(Ham.FIELDS, Fields( - Field('w', Spam), - Field('x', int), - Field('y', Union(int, str)), - Field('z', Array(int)), - )) - self.assertEqual(Eggs.FIELDS, Fields( - Field('b', Array(Ham)), - Field('x', - Array(Union.unordered(str, Array(Eggs))), - optional=True), - Field('d', Union.unordered(Spam, Eggs), optional=True), - )) - - def test_normalize_missing(self): - with self.assertRaises(TypeError): - FieldsNamespace.normalize() - - ####### - - def test_bind_no_param(self): - class Spam(FieldsNamespace): - FIELDS = [ - Field('a'), - ] - - arg = Spam.bind({'a': 'x'}) - - self.assertIsInstance(arg, Spam) - self.assertEqual(arg, Spam(a='x')) - - def test_bind_with_param_obj(self): - class Param(Parameter): - HANDLER = DatatypeHandler(ANY) - match_type = (lambda self, raw: self.HANDLER) - - class Spam(FieldsNamespace): - PARAM = Param(ANY) - FIELDS = [ - Field('a'), - ] - - arg = Spam.bind({'a': 'x'}) - - self.assertIsInstance(arg, Arg) - self.assertEqual(arg, Arg(Param(ANY), {'a': 'x'})) - - def test_bind_with_param_type(self): - class Param(Parameter): - HANDLER = DatatypeHandler(ANY) - match_type = (lambda self, raw: self.HANDLER) - - class Spam(FieldsNamespace): - PARAM_TYPE = Param - FIELDS = [ - Field('a'), - ] - - arg = Spam.bind({'a': 'x'}) - - self.assertIsInstance(arg, Arg) - self.assertEqual(arg, Arg(Param(Spam.FIELDS), {'a': 'x'})) - - def test_bind_already_bound(self): - class Spam(FieldsNamespace): - FIELDS = [ - Field('a'), - ] - - spam = Spam(a='x') - arg = Spam.bind(spam) - - self.assertIs(arg, spam) - - ####### - - def test_fields_full(self): - class Spam(FieldsNamespace): - FIELDS = FIELDS_EXTENDED - - spam = Spam(**EXTENDED_FULL) - ns = vars(spam) - del ns['_validators'] - del ns['_serializers'] - - self.assertEqual(ns, { - 'name': 'spam', - 'valid': True, - 'id': 10, - 'value': None, - 'x': Basic(**BASIC_FULL), - 'y': 11, - 'z': [ - Basic(**BASIC_FULL), - Basic(**BASIC_MIN), - ], - }) - - def test_fields_min(self): - class Spam(FieldsNamespace): - FIELDS = FIELDS_EXTENDED - - spam = Spam(**EXTENDED_MIN) - ns = vars(spam) - del ns['_validators'] - del ns['_serializers'] - - self.assertEqual(ns, { - 'name': 'spam', - 'id': 10, - }) - - def test_no_fields(self): - with self.assertRaises(TypeError): - FieldsNamespace( - x='spam', - y=42, - z=None, - ) - - def test_attrs(self): - ns = Basic(name='', value='') - - self.assertEqual(ns.name, '') - self.assertEqual(ns.value, '') - - def test_equality(self): - ns1 = Basic(name='', value='') - ns2 = Basic(name='', value='') - - self.assertTrue(ns1 == ns1) - self.assertTrue(ns1 == ns2) - - def test_inequality(self): - p = [Basic(name=n, value=v) - for n in ['<>', ''] - for v in ['<>', '']] - for basic1, basic2 in itertools.combinations(p, 2): - with self.subTest((basic1, basic2)): - self.assertTrue(basic1 != basic2) - - @unittest.skip('not ready') - def test_validate(self): - # TODO: finish - raise NotImplementedError - - def test_as_data(self): - class Spam(FieldsNamespace): - FIELDS = FIELDS_EXTENDED - - spam = Spam(**EXTENDED_FULL) - sdata = spam.as_data() - basic = Basic(**BASIC_FULL) - bdata = basic.as_data() - - self.assertEqual(sdata, EXTENDED_FULL) - self.assertEqual(bdata, BASIC_FULL) diff --git a/tests/debugger_protocol/arg/test__decl.py b/tests/debugger_protocol/arg/test__decl.py deleted file mode 100644 index 2aa953ee..00000000 --- a/tests/debugger_protocol/arg/test__decl.py +++ /dev/null @@ -1,565 +0,0 @@ -import unittest - -from debugger_protocol.arg import NOT_SET, ANY -from debugger_protocol.arg._datatype import FieldsNamespace -from debugger_protocol.arg._decl import ( - REF, TYPE_REFERENCE, _normalize_datatype, _transform_datatype, - Enum, Union, Array, Mapping, Field, Fields) -from debugger_protocol.arg._param import Parameter, DatatypeHandler, Arg -from debugger_protocol.arg._params import ( - SimpleParameter, UnionParameter, ArrayParameter, ComplexParameter) - - -class ModuleTests(unittest.TestCase): - - def test_normalize_datatype(self): - class Spam: - @classmethod - def normalize(cls): - return OKAY - - OKAY = object() - NOOP = object() - param = SimpleParameter(str) - tests = [ - # explicitly handled - (REF, TYPE_REFERENCE), - (TYPE_REFERENCE, NOOP), - (ANY, NOOP), - (None, NOOP), - (int, NOOP), - (str, NOOP), - (bool, NOOP), - (Enum(str, ('spam',)), NOOP), - (Union(str, int), NOOP), - ({str, int}, Union(str, int)), - (frozenset([str, int]), Union(str, int)), - (Array(str), NOOP), - ([str], Array(str)), - ((str,), Array(str)), - (Mapping(str), NOOP), - ({str: str}, Mapping(str)), - # others - (Field('spam'), NOOP), - (Fields(Field('spam')), NOOP), - (param, NOOP), - (DatatypeHandler(str), NOOP), - (Arg(param, 'spam'), NOOP), - (SimpleParameter(str), NOOP), - (UnionParameter(Union(str)), NOOP), - (ArrayParameter(Array(str)), NOOP), - (ComplexParameter(Fields()), NOOP), - (NOT_SET, NOOP), - (object(), NOOP), - (object, NOOP), - (type, NOOP), - (Spam, OKAY), - ] - for datatype, expected in tests: - if expected is NOOP: - expected = datatype - with self.subTest(datatype): - datatype = _normalize_datatype(datatype) - - self.assertEqual(datatype, expected) - - def test_transform_datatype_simple(self): - datatypes = [ - REF, - TYPE_REFERENCE, - ANY, - None, - int, - str, - bool, - {str, int}, - frozenset([str, int]), - [str], - (str,), - Parameter(object()), - DatatypeHandler(str), - Arg(SimpleParameter(str), 'spam'), - SimpleParameter(str), - UnionParameter(Union(str, int)), - ArrayParameter(Array(str)), - ComplexParameter(Fields()), - NOT_SET, - object(), - object, - type, - ] - for expected in datatypes: - transformed = [] - op = (lambda dt: transformed.append(dt) or dt) - with self.subTest(expected): - datatype = _transform_datatype(expected, op) - - self.assertIs(datatype, expected) - self.assertEqual(transformed, [expected]) - - def test_transform_datatype_container(self): - class Spam(FieldsNamespace): - FIELDS = [ - Field('a'), - Field('b', {str: str}) - ] - - fields = Fields(Field('...')) - field_spam = Field('spam', ANY) - field_ham = Field('ham', Union( - Array(Spam), - )) - field_eggs = Field('eggs', Array(TYPE_REFERENCE)) - nested = Fields( - Field('???', fields), - field_spam, - field_ham, - field_eggs, - ) - tests = { - Array(str): [ - Array(str), - str, - ], - Field('...'): [ - Field('...'), - str, - ], - fields: [ - fields, - Field('...'), - str, - ], - nested: [ - nested, - # ... - Field('???', fields), - fields, - Field('...'), - str, - # ... - Field('spam', ANY), - ANY, - # ... - field_ham, - Union(Array(Spam)), - Array(Spam), - Spam, - Field('a'), - str, - Field('b', Mapping(str)), - Mapping(str), - str, - str, - # ... - field_eggs, - Array(TYPE_REFERENCE), - TYPE_REFERENCE, - ], - } - self.maxDiff = None - for datatype, expected in tests.items(): - calls = [] - op = (lambda dt: calls.append(dt) or dt) - with self.subTest(datatype): - transformed = _transform_datatype(datatype, op) - - self.assertIs(transformed, datatype) - self.assertEqual(calls, expected) - - # Check Union separately due to set iteration order. - calls = [] - op = (lambda dt: calls.append(dt) or dt) - datatype = Union(str, int) - transformed = _transform_datatype(datatype, op) - - self.assertIs(transformed, datatype) - self.assertEqual(calls[0], Union(str, int)) - self.assertEqual(set(calls[1:]), {str, int}) - - -class EnumTests(unittest.TestCase): - - def test_attrs(self): - enum = Enum(str, ('spam', 'eggs')) - datatype, choices = enum - - self.assertIs(datatype, str) - self.assertEqual(choices, frozenset(['spam', 'eggs'])) - - def test_bad_datatype(self): - with self.assertRaises(ValueError): - Enum('spam', ('spam', 'eggs')) - with self.assertRaises(ValueError): - Enum(dict, ('spam', 'eggs')) - - def test_bad_choices(self): - class String(str): - pass - - with self.assertRaises(ValueError): - Enum(str, 'spam') - with self.assertRaises(TypeError): - Enum(str, ()) - with self.assertRaises(ValueError): - Enum(str, ('spam', 10)) - with self.assertRaises(ValueError): - Enum(str, ('spam', String)) - - -class UnionTests(unittest.TestCase): - - def test_normalized(self): - tests = [ - (REF, TYPE_REFERENCE), - ({str, int}, Union(*{str, int})), - (frozenset([str, int]), Union(*frozenset([str, int]))), - ([str], Array(str)), - ((str,), Array(str)), - ({str: str}, Mapping(str)), - (None, None), - ] - for datatype, expected in tests: - with self.subTest(datatype): - union = Union(int, datatype, str) - - self.assertEqual(union, Union(int, expected, str)) - - def test_traverse_noop(self): - calls = [] - op = (lambda dt: calls.append(dt) or dt) - union = Union(str, Array(int), int) - transformed = union.traverse(op) - - self.assertIs(transformed, union) - self.assertCountEqual(calls, [ - str, - # Note that it did not recurse into Array(int). - Array(int), - int, - ]) - - def test_traverse_changed(self): - calls = [] - op = (lambda dt: calls.append(dt) or str) - union = Union(ANY) - transformed = union.traverse(op) - - self.assertIsNot(transformed, union) - self.assertEqual(transformed, Union(str)) - self.assertEqual(calls, [ - ANY, - ]) - - -class ArrayTests(unittest.TestCase): - - def test_normalized(self): - tests = [ - (REF, TYPE_REFERENCE), - ({str, int}, Union(str, int)), - (frozenset([str, int]), Union(str, int)), - ([str], Array(str)), - ((str,), Array(str)), - ({str: str}, Mapping(str)), - (None, None), - ] - for datatype, expected in tests: - with self.subTest(datatype): - array = Array(datatype) - - self.assertEqual(array, Array(expected)) - - def test_normalized_transformed(self): - calls = 0 - - class Spam: - @classmethod - def traverse(cls, op): - nonlocal calls - calls += 1 - return cls - - array = Array(Spam) - - self.assertIs(array.itemtype, Spam) - self.assertEqual(calls, 1) - - def test_traverse_noop(self): - calls = [] - op = (lambda dt: calls.append(dt) or dt) - array = Array(Union(str, int)) - transformed = array.traverse(op) - - self.assertIs(transformed, array) - self.assertCountEqual(calls, [ - # Note that it did not recurse into Union(str, int). - Union(str, int), - ]) - - def test_traverse_changed(self): - calls = [] - op = (lambda dt: calls.append(dt) or str) - array = Array(ANY) - transformed = array.traverse(op) - - self.assertIsNot(transformed, array) - self.assertEqual(transformed, Array(str)) - self.assertEqual(calls, [ - ANY, - ]) - - -class MappingTests(unittest.TestCase): - - def test_normalized(self): - tests = [ - (REF, TYPE_REFERENCE), - ({str, int}, Union(str, int)), - (frozenset([str, int]), Union(str, int)), - ([str], Array(str)), - ((str,), Array(str)), - ({str: str}, Mapping(str)), - (None, None), - ] - for datatype, expected in tests: - with self.subTest(datatype): - mapping = Mapping(datatype) - - self.assertEqual(mapping, Mapping(expected)) - - def test_normalized_transformed(self): - calls = 0 - - class Spam: - @classmethod - def traverse(cls, op): - nonlocal calls - calls += 1 - return cls - - mapping = Mapping(Spam) - - self.assertIs(mapping.keytype, str) - self.assertIs(mapping.valuetype, Spam) - self.assertEqual(calls, 1) - - def test_traverse_noop(self): - calls = [] - op = (lambda dt: calls.append(dt) or dt) - mapping = Mapping(Union(str, int)) - transformed = mapping.traverse(op) - - self.assertIs(transformed, mapping) - self.assertCountEqual(calls, [ - str, - # Note that it did not recurse into Union(str, int). - Union(str, int), - ]) - - def test_traverse_changed(self): - calls = [] - op = (lambda dt: calls.append(dt) or str) - mapping = Mapping(ANY) - transformed = mapping.traverse(op) - - self.assertIsNot(transformed, mapping) - self.assertEqual(transformed, Mapping(str)) - self.assertEqual(calls, [ - str, - ANY, - ]) - - -class FieldTests(unittest.TestCase): - - def test_defaults(self): - field = Field('spam') - - self.assertEqual(field.name, 'spam') - self.assertIs(field.datatype, str) - self.assertIs(field.default, NOT_SET) - self.assertFalse(field.optional) - - def test_enum(self): - field = Field('spam', str, enum=('a', 'b', 'c')) - - self.assertEqual(field.datatype, Enum(str, ('a', 'b', 'c'))) - - def test_normalized(self): - tests = [ - (REF, TYPE_REFERENCE), - ({str, int}, Union(str, int)), - (frozenset([str, int]), Union(str, int)), - ([str], Array(str)), - ((str,), Array(str)), - ({str: str}, Mapping(str)), - (None, None), - ] - for datatype, expected in tests: - with self.subTest(datatype): - field = Field('spam', datatype) - - self.assertEqual(field, Field('spam', expected)) - - def test_normalized_transformed(self): - calls = 0 - - class Spam: - @classmethod - def traverse(cls, op): - nonlocal calls - calls += 1 - return cls - - field = Field('spam', Spam) - - self.assertIs(field.datatype, Spam) - self.assertEqual(calls, 1) - - def test_traverse_noop(self): - calls = [] - op = (lambda dt: calls.append(dt) or dt) - field = Field('spam', Union(str, int)) - transformed = field.traverse(op) - - self.assertIs(transformed, field) - self.assertCountEqual(calls, [ - # Note that it did not recurse into Union(str, int). - Union(str, int), - ]) - - def test_traverse_changed(self): - calls = [] - op = (lambda dt: calls.append(dt) or str) - field = Field('spam', ANY) - transformed = field.traverse(op) - - self.assertIsNot(transformed, field) - self.assertEqual(transformed, Field('spam', str)) - self.assertEqual(calls, [ - ANY, - ]) - - -class FieldsTests(unittest.TestCase): - - def test_single(self): - fields = Fields( - Field('spam'), - ) - - self.assertEqual(fields, [ - Field('spam'), - ]) - - def test_multiple(self): - fields = Fields( - Field('spam'), - Field('ham'), - Field('eggs'), - ) - - self.assertEqual(fields, [ - Field('spam'), - Field('ham'), - Field('eggs'), - ]) - - def test_empty(self): - fields = Fields() - - self.assertCountEqual(fields, []) - - def test_normalized(self): - tests = [ - (REF, TYPE_REFERENCE), - ({str, int}, Union(str, int)), - (frozenset([str, int]), Union(str, int)), - ([str], Array(str)), - ((str,), Array(str)), - ({str: str}, Mapping(str)), - (None, None), - ] - for datatype, expected in tests: - with self.subTest(datatype): - fields = Fields( - Field('spam', datatype), - ) - - self.assertEqual(fields, [ - Field('spam', expected), - ]) - - def test_with_START_OPTIONAL(self): - fields = Fields( - Field('spam'), - Field('ham', optional=True), - Field('eggs'), - Field.START_OPTIONAL, - Field('a'), - Field('b', optional=False), - ) - - self.assertEqual(fields, [ - Field('spam'), - Field('ham', optional=True), - Field('eggs'), - Field('a', optional=True), - Field('b', optional=True), - ]) - - def test_non_field(self): - with self.assertRaises(TypeError): - Fields(str) - - def test_as_dict(self): - fields = Fields( - Field('spam', int), - Field('ham'), - Field('eggs', Array(str)), - ) - result = fields.as_dict() - - self.assertEqual(result, { - 'spam': fields[0], - 'ham': fields[1], - 'eggs': fields[2], - }) - - def test_traverse_noop(self): - calls = [] - op = (lambda dt: calls.append(dt) or dt) - fields = Fields( - Field('spam'), - Field('ham'), - Field('eggs'), - ) - transformed = fields.traverse(op) - - self.assertIs(transformed, fields) - self.assertCountEqual(calls, [ - # Note that it did not recurse into the fields. - Field('spam'), - Field('ham'), - Field('eggs'), - ]) - - def test_traverse_changed(self): - calls = [] - op = (lambda dt: calls.append(dt) or Field(dt.name, str)) - fields = Fields( - Field('spam', ANY), - Field('eggs', None), - ) - transformed = fields.traverse(op) - - self.assertIsNot(transformed, fields) - self.assertEqual(transformed, Fields( - Field('spam', str), - Field('eggs', str), - )) - self.assertEqual(calls, [ - Field('spam', ANY), - Field('eggs', None), - ]) diff --git a/tests/debugger_protocol/arg/test__param.py b/tests/debugger_protocol/arg/test__param.py deleted file mode 100644 index 47236a68..00000000 --- a/tests/debugger_protocol/arg/test__param.py +++ /dev/null @@ -1,237 +0,0 @@ -from types import SimpleNamespace -import unittest - -from debugger_protocol.arg._param import Parameter, DatatypeHandler, Arg - -from tests.helpers.stub import Stub - - -class FakeHandler(DatatypeHandler): - - def __init__(self, datatype=str, stub=None): - super().__init__(datatype) - self.stub = stub or Stub() - self.returns = SimpleNamespace( - coerce=None, - as_data=None, - ) - - def coerce(self, raw): - self.stub.add_call('coerce', raw) - self.stub.maybe_raise() - return self.returns.coerce - - def validate(self, coerced): - self.stub.add_call('validate', coerced) - self.stub.maybe_raise() - - def as_data(self, coerced): - self.stub.add_call('as_data', coerced) - self.stub.maybe_raise() - return self.returns.as_data - - -class ParameterTests(unittest.TestCase): - - def setUp(self): - super().setUp() - self.stub = Stub() - self.handler = FakeHandler(self.stub) - - def test_bind_matched(self): - param = Parameter(str, self.handler) - arg = param.bind('spam') - - self.assertEqual(arg, Arg(param, 'spam', self.handler)) - self.assertEqual(self.stub.calls, []) - - def test_bind_no_match(self): - param = Parameter(str) - - arg = param.bind('spam') - self.assertIs(arg, None) - self.assertEqual(self.stub.calls, []) - - def test_match_type_no_match(self): - param = Parameter(str) - matched = param.match_type('spam') - - self.assertIs(matched, None) - self.assertEqual(self.stub.calls, []) - - def test_match_type_matched(self): - param = Parameter(str, self.handler) - matched = param.match_type('spam') - - self.assertIs(matched, self.handler) - self.assertEqual(self.stub.calls, []) - - -class DatatypeHandlerTests(unittest.TestCase): - - def test_coerce(self): - handler = DatatypeHandler(str) - coerced = handler.coerce('spam') - - self.assertEqual(coerced, 'spam') - - def test_validate(self): - handler = DatatypeHandler(str) - handler.validate('spam') - - def test_as_data(self): - handler = DatatypeHandler(str) - data = handler.as_data('spam') - - self.assertEqual(data, 'spam') - - -class ArgTests(unittest.TestCase): - - def setUp(self): - super().setUp() - self.stub = Stub() - self.handler = FakeHandler(str, self.stub) - self.param = Parameter(str, self.handler) - - def test_raw_valid(self): - self.handler.returns.coerce = 'eggs' - arg = Arg(self.param, 'spam', self.handler) - raw = arg.raw - - self.assertEqual(raw, 'spam') - self.assertEqual(self.stub.calls, [ - ('coerce', ('spam',), {}), - ('validate', ('eggs',), {}), - ]) - - def test_raw_invalid(self): - self.handler.returns.coerce = 'eggs' - self.stub.set_exceptions( - None, - ValueError('oops'), - ) - arg = Arg(self.param, 'spam', self.handler) - - with self.assertRaises(ValueError): - arg.raw - self.assertEqual(self.stub.calls, [ - ('coerce', ('spam',), {}), - ('validate', ('eggs',), {}), - ]) - - def test_raw_generated(self): - self.handler.returns.as_data = 'spam' - arg = Arg(self.param, 'eggs', self.handler, israw=False) - raw = arg.raw - - self.assertEqual(raw, 'spam') - self.assertEqual(self.stub.calls, [ - ('validate', ('eggs',), {}), - ('as_data', ('eggs',), {}), - ]) - - def test_value_valid(self): - arg = Arg(self.param, 'eggs', self.handler, israw=False) - value = arg.value - - self.assertEqual(value, 'eggs') - self.assertEqual(self.stub.calls, [ - ('validate', ('eggs',), {}), - ]) - - def test_value_invalid(self): - self.stub.set_exceptions( - ValueError('oops'), - ) - arg = Arg(self.param, 'eggs', self.handler, israw=False) - - with self.assertRaises(ValueError): - arg.value - self.assertEqual(self.stub.calls, [ - ('validate', ('eggs',), {}), - ]) - - def test_value_generated(self): - self.handler.returns.coerce = 'eggs' - arg = Arg(self.param, 'spam', self.handler) - value = arg.value - - self.assertEqual(value, 'eggs') - self.assertEqual(self.stub.calls, [ - ('coerce', ('spam',), {}), - ('validate', ('eggs',), {}), - ]) - - def test_coerce(self): - self.handler.returns.coerce = 'eggs' - arg = Arg(self.param, 'spam', self.handler) - value = arg.coerce() - - self.assertEqual(value, 'eggs') - self.assertEqual(self.stub.calls, [ - ('coerce', ('spam',), {}), - ]) - - def test_validate_okay(self): - self.handler.returns.coerce = 'eggs' - arg = Arg(self.param, 'spam', self.handler) - arg.validate() - - self.assertEqual(self.stub.calls, [ - ('coerce', ('spam',), {}), - ('validate', ('eggs',), {}), - ]) - - def test_validate_invalid(self): - self.stub.set_exceptions( - None, - ValueError('oops'), - ) - self.handler.returns.coerce = 'eggs' - arg = Arg(self.param, 'spam', self.handler) - - with self.assertRaises(ValueError): - arg.validate() - self.assertEqual(self.stub.calls, [ - ('coerce', ('spam',), {}), - ('validate', ('eggs',), {}), - ]) - - def test_validate_use_coerced(self): - handler = FakeHandler() - other = Arg(Parameter(str, handler), 'spam', handler, israw=False) - arg = Arg(Parameter(str, self.handler), other, self.handler, - israw=False) - arg.validate() - - self.assertEqual(self.stub.calls, []) - self.assertEqual(handler.stub.calls, [ - ('validate', ('spam',), {}), - ]) - - def test_as_data_use_handler(self): - self.handler.returns.as_data = 'spam' - arg = Arg(self.param, 'eggs', self.handler, israw=False) - data = arg.as_data() - - self.assertEqual(data, 'spam') - self.assertEqual(self.stub.calls, [ - ('validate', ('eggs',), {}), - ('as_data', ('eggs',), {}), - ]) - - def test_as_data_use_coerced(self): - handler = FakeHandler() - other = Arg(Parameter(str, handler), 'spam', handler, israw=False) - handler.returns.as_data = 'spam' - arg = Arg(Parameter(str, self.handler), other, self.handler, - israw=False) - data = arg.as_data(other) - - self.assertEqual(data, 'spam') - self.assertEqual(self.stub.calls, []) - self.assertEqual(handler.stub.calls, [ - ('validate', ('spam',), {}), - ('as_data', ('spam',), {}), - ]) diff --git a/tests/debugger_protocol/arg/test__params.py b/tests/debugger_protocol/arg/test__params.py deleted file mode 100644 index 64aa59f3..00000000 --- a/tests/debugger_protocol/arg/test__params.py +++ /dev/null @@ -1,800 +0,0 @@ -import unittest - -from debugger_protocol.arg._common import NOT_SET, ANY -from debugger_protocol.arg._decl import ( - Enum, Union, Array, Mapping, Field, Fields) -from debugger_protocol.arg._param import Parameter, DatatypeHandler -from debugger_protocol.arg._params import ( - param_from_datatype, - NoopParameter, SingletonParameter, - SimpleParameter, EnumParameter, - UnionParameter, ArrayParameter, MappingParameter, ComplexParameter) - -from ._common import FIELDS_BASIC, BASIC_FULL, Basic - - -class String(str): - pass - - -class Integer(int): - pass - - -class ParamFromDatatypeTest(unittest.TestCase): - - def test_supported(self): - handler = DatatypeHandler(str) - tests = [ - (Parameter(str), Parameter(str)), - (handler, Parameter(str, handler)), - (Fields(Field('spam')), ComplexParameter(Fields(Field('spam')))), - (Field('spam'), SimpleParameter(str)), - (Field('spam', str, enum={'a'}), EnumParameter(str, {'a'})), - (ANY, NoopParameter()), - (None, SingletonParameter(None)), - (str, SimpleParameter(str)), - (int, SimpleParameter(int)), - (bool, SimpleParameter(bool)), - (Enum(str, {'a'}), EnumParameter(str, {'a'})), - (Union(str, int), UnionParameter(Union(str, int))), - ({str, int}, UnionParameter(Union(str, int))), - (frozenset([str, int]), UnionParameter(Union(str, int))), - (Array(str), ArrayParameter(Array(str))), - ([str], ArrayParameter(Array(str))), - ((str,), ArrayParameter(Array(str))), - (Basic, ComplexParameter(Basic)), - ] - for datatype, expected in tests: - with self.subTest(datatype): - param = param_from_datatype(datatype) - - self.assertEqual(param, expected) - - def test_not_supported(self): - datatypes = [ - String('spam'), - ..., - ] - for datatype in datatypes: - with self.subTest(datatype): - with self.assertRaises(NotImplementedError): - param_from_datatype(datatype) - - -class NoopParameterTests(unittest.TestCase): - - VALUES = [ - object(), - 'spam', - 10, - ['spam'], - {'spam': 42}, - True, - None, - NOT_SET, - ] - - def test_match_type(self): - values = [ - object(), - '', - 'spam', - b'spam', - 0, - 10, - 10.0, - 10+0j, - ('spam',), - (), - ['spam'], - [], - {'spam': 42}, - {}, - {'spam'}, - set(), - object, - type, - NoopParameterTests, - True, - None, - ..., - NotImplemented, - NOT_SET, - ANY, - Union(str, int), - Union(), - Array(str), - Field('spam'), - Fields(Field('spam')), - Fields(), - Basic, - ] - for value in values: - with self.subTest(value): - param = NoopParameter() - handler = param.match_type(value) - - self.assertIs(type(handler), DatatypeHandler) - self.assertIs(handler.datatype, ANY) - - def test_coerce(self): - for value in self.VALUES: - with self.subTest(value): - param = NoopParameter() - handler = param.match_type(value) - coerced = handler.coerce(value) - - self.assertIs(coerced, value) - - def test_validate(self): - for value in self.VALUES: - with self.subTest(value): - param = NoopParameter() - handler = param.match_type(value) - handler.validate(value) - - def test_as_data(self): - for value in self.VALUES: - with self.subTest(value): - param = NoopParameter() - handler = param.match_type(value) - data = handler.as_data(value) - - self.assertIs(data, value) - - -class SingletonParameterTests(unittest.TestCase): - - def test_match_type_matched(self): - param = SingletonParameter(None) - handler = param.match_type(None) - - self.assertIs(handler.datatype, None) - - def test_match_type_no_match(self): - tests = [ - # same type, different value - ('spam', 'eggs'), - (10, 11), - (True, False), - # different type but equivalent - ('spam', b'spam'), - (10, 10.0), - (10, 10+0j), - (10, '10'), - (10, b'\10'), - ] - for singleton, value in tests: - with self.subTest((singleton, value)): - param = SingletonParameter(singleton) - handler = param.match_type(value) - - self.assertIs(handler, None) - - def test_coerce(self): - param = SingletonParameter(None) - handler = param.match_type(None) - value = handler.coerce(None) - - self.assertIs(value, None) - - def test_validate_valid(self): - param = SingletonParameter(None) - handler = param.match_type(None) - handler.validate(None) - - def test_validate_wrong_type(self): - tests = [ - (None, True), - (True, None), - ('spam', 10), - (10, 'spam'), - ] - for singleton, value in tests: - with self.subTest(singleton): - param = SingletonParameter(singleton) - handler = param.match_type(singleton) - - with self.assertRaises(ValueError): - handler.validate(value) - - def test_validate_same_type_wrong_value(self): - tests = [ - ('spam', 'eggs'), - (True, False), - (10, 11), - ] - for singleton, value in tests: - with self.subTest(singleton): - param = SingletonParameter(singleton) - handler = param.match_type(singleton) - - with self.assertRaises(ValueError): - handler.validate(value) - - def test_as_data(self): - param = SingletonParameter(None) - handler = param.match_type(None) - data = handler.as_data(None) - - self.assertIs(data, None) - - -class SimpleParameterTests(unittest.TestCase): - - def test_match_type_match(self): - tests = [ - (str, 'spam'), - (str, String('spam')), - (int, 10), - (bool, True), - ] - for datatype, value in tests: - with self.subTest((datatype, value)): - param = SimpleParameter(datatype, strict=False) - handler = param.match_type(value) - - self.assertIs(handler.datatype, datatype) - - def test_match_type_no_match(self): - tests = [ - (int, 'spam'), - # coercible - (str, 10), - (int, 10.0), - (int, '10'), - (bool, 1), - # semi-coercible - (str, b'spam'), - (int, 10+0j), - (int, b'\10'), - ] - for datatype, value in tests: - with self.subTest((datatype, value)): - param = SimpleParameter(datatype, strict=False) - handler = param.match_type(value) - - self.assertIs(handler, None) - - def test_match_type_strict_match(self): - tests = { - str: 'spam', - int: 10, - bool: True, - } - for datatype, value in tests.items(): - with self.subTest(datatype): - param = SimpleParameter(datatype, strict=True) - handler = param.match_type(value) - - self.assertIs(handler.datatype, datatype) - - def test_match_type_strict_no_match(self): - tests = { - str: String('spam'), - int: Integer(10), - } - for datatype, value in tests.items(): - with self.subTest(datatype): - param = SimpleParameter(datatype, strict=True) - handler = param.match_type(value) - - self.assertIs(handler, None) - - def test_coerce(self): - tests = [ - (str, 'spam', 'spam'), - (str, String('spam'), 'spam'), - (int, 10, 10), - (bool, True, True), - # did not match, but still coercible - (str, 10, '10'), - (str, str, ""), - (int, 10.0, 10), - (int, '10', 10), - (bool, 1, True), - ] - for datatype, value, expected in tests: - with self.subTest((datatype, value)): - handler = SimpleParameter.HANDLER(datatype) - coerced = handler.coerce(value) - - self.assertEqual(coerced, expected) - - def test_validate_valid(self): - tests = { - str: 'spam', - int: 10, - bool: True, - } - for datatype, value in tests.items(): - with self.subTest(datatype): - handler = SimpleParameter.HANDLER(datatype) - handler.validate(value) - - def test_validate_invalid(self): - tests = [ - (int, 'spam'), - # coercible - (str, String('spam')), - (str, 10), - (int, 10.0), - (int, '10'), - (bool, 1), - # semi-coercible - (str, b'spam'), - (int, 10+0j), - (int, b'\10'), - ] - for datatype, value in tests: - with self.subTest((datatype, value)): - handler = SimpleParameter.HANDLER(datatype) - - with self.assertRaises(ValueError): - handler.validate(value) - - def test_as_data(self): - tests = [ - (str, 'spam'), - (int, 10), - (bool, True), - # did not match, but still coercible - (str, String('spam')), - (str, 10), - (str, str), - (int, 10.0), - (int, '10'), - (bool, 1), - # semi-coercible - (str, b'spam'), - (int, 10+0j), - (int, b'\10'), - ] - for datatype, value in tests: - with self.subTest((datatype, value)): - handler = SimpleParameter.HANDLER(datatype) - data = handler.as_data(value) - - self.assertIs(data, value) - - -class EnumParameterTests(unittest.TestCase): - - def test_match_type_match(self): - tests = [ - (str, ('spam', 'eggs'), 'spam'), - (str, ('spam',), 'spam'), - (int, (1, 2, 3), 2), - (bool, (True,), True), - ] - for datatype, enum, value in tests: - with self.subTest((datatype, enum)): - param = EnumParameter(datatype, enum) - handler = param.match_type(value) - - self.assertIs(handler.datatype, datatype) - - def test_match_type_no_match(self): - tests = [ - # enum mismatch - (str, ('spam', 'eggs'), 'ham'), - (int, (1, 2, 3), 10), - # type mismatch - (int, (1, 2, 3), 'spam'), - # coercible - (str, ('spam', 'eggs'), String('spam')), - (str, ('1', '2', '3'), 2), - (int, (1, 2, 3), 2.0), - (int, (1, 2, 3), '2'), - (bool, (True,), 1), - # semi-coercible - (str, ('spam', 'eggs'), b'spam'), - (int, (1, 2, 3), 2+0j), - (int, (1, 2, 3), b'\02'), - ] - for datatype, enum, value in tests: - with self.subTest((datatype, enum, value)): - param = EnumParameter(datatype, enum) - handler = param.match_type(value) - - self.assertIs(handler, None) - - def test_coerce(self): - tests = [ - (str, 'spam', 'spam'), - (int, 10, 10), - (bool, True, True), - # did not match, but still coercible - (str, String('spam'), 'spam'), - (str, 10, '10'), - (str, str, ""), - (int, 10.0, 10), - (int, '10', 10), - (bool, 1, True), - ] - for datatype, value, expected in tests: - with self.subTest((datatype, value)): - enum = (expected,) - handler = EnumParameter.HANDLER(datatype, enum) - coerced = handler.coerce(value) - - self.assertEqual(coerced, expected) - - def test_coerce_enum_mismatch(self): - enum = ('spam', 'eggs') - handler = EnumParameter.HANDLER(str, enum) - coerced = handler.coerce('ham') - - # It still works. - self.assertEqual(coerced, 'ham') - - def test_validate_valid(self): - tests = [ - (str, ('spam', 'eggs'), 'spam'), - (str, ('spam',), 'spam'), - (int, (1, 2, 3), 2), - (bool, (True, False), True), - ] - for datatype, enum, value in tests: - with self.subTest((datatype, enum)): - handler = EnumParameter.HANDLER(datatype, enum) - handler.validate(value) - - def test_validate_invalid(self): - tests = [ - # enum mismatch - (str, ('spam', 'eggs'), 'ham'), - (int, (1, 2, 3), 10), - # type mismatch - (int, (1, 2, 3), 'spam'), - # coercible - (str, ('spam', 'eggs'), String('spam')), - (str, ('1', '2', '3'), 2), - (int, (1, 2, 3), 2.0), - (int, (1, 2, 3), '2'), - (bool, (True,), 1), - # semi-coercible - (str, ('spam', 'eggs'), b'spam'), - (int, (1, 2, 3), 2+0j), - (int, (1, 2, 3), b'\02'), - ] - for datatype, enum, value in tests: - with self.subTest((datatype, enum, value)): - handler = EnumParameter.HANDLER(datatype, enum) - - with self.assertRaises(ValueError): - handler.validate(value) - - def test_as_data(self): - tests = [ - (str, ('spam', 'eggs'), 'spam'), - (str, ('spam',), 'spam'), - (int, (1, 2, 3), 2), - (bool, (True,), True), - # enum mismatch - (str, ('spam', 'eggs'), 'ham'), - (int, (1, 2, 3), 10), - # type mismatch - (int, (1, 2, 3), 'spam'), - # coercible - (str, ('spam', 'eggs'), String('spam')), - (str, ('1', '2', '3'), 2), - (int, (1, 2, 3), 2.0), - (int, (1, 2, 3), '2'), - (bool, (True,), 1), - # semi-coercible - (str, ('spam', 'eggs'), b'spam'), - (int, (1, 2, 3), 2+0j), - (int, (1, 2, 3), b'\02'), - ] - for datatype, enum, value in tests: - with self.subTest((datatype, enum, value)): - handler = EnumParameter.HANDLER(datatype, enum) - data = handler.as_data(value) - - self.assertIs(data, value) - - -class UnionParameterTests(unittest.TestCase): - - def test_match_type_all_simple(self): - tests = [ - 'spam', - 10, - True, - ] - datatype = Union(str, int, bool) - param = UnionParameter(datatype) - for value in tests: - with self.subTest(value): - handler = param.match_type(value) - - self.assertIs(type(handler), SimpleParameter.HANDLER) - self.assertIs(handler.datatype, type(value)) - - def test_match_type_mixed(self): - datatype = Union( - str, - # XXX add dedicated enums - Enum(int, (1, 2, 3)), - Basic, - Array(str), - Array(int), - Union(int, bool), - ) - param = UnionParameter(datatype) - - tests = [ - ('spam', SimpleParameter.HANDLER(str)), - (2, EnumParameter.HANDLER(int, (1, 2, 3))), - (BASIC_FULL, ComplexParameter(Basic).match_type(BASIC_FULL)), - (['spam'], ArrayParameter.HANDLER(Array(str))), - ([], ArrayParameter.HANDLER(Array(str))), - ([10], ArrayParameter.HANDLER(Array(int))), - (10, SimpleParameter.HANDLER(int)), - (True, SimpleParameter.HANDLER(bool)), - # no match - (Integer(2), None), - ([True], None), - ({}, None), - ] - for value, expected in tests: - with self.subTest(value): - handler = param.match_type(value) - - self.assertEqual(handler, expected) - - def test_match_type_catchall(self): - NOOP = DatatypeHandler(ANY) - param = UnionParameter(Union(int, str, ANY)) - tests = [ - ('spam', SimpleParameter.HANDLER(str)), - (10, SimpleParameter.HANDLER(int)), - # catchall - (BASIC_FULL, NOOP), - (['spam'], NOOP), - (True, NOOP), - (Integer(2), NOOP), - ([10], NOOP), - ({}, NOOP), - ] - for value, expected in tests: - with self.subTest(value): - handler = param.match_type(value) - - self.assertEqual(handler, expected) - - def test_match_type_no_match(self): - param = UnionParameter(Union(int, str)) - values = [ - BASIC_FULL, - ['spam'], - True, - Integer(2), - [10], - {}, - ] - for value in values: - with self.subTest(value): - handler = param.match_type(value) - - self.assertIs(handler, None) - - -class ArrayParameterTests(unittest.TestCase): - - def test_match_type_match(self): - param = ArrayParameter(Array(str)) - expected = ArrayParameter.HANDLER(Array(str)) - values = [ - ['a', 'b', 'c'], - [], - ] - for value in values: - with self.subTest(value): - handler = param.match_type(value) - - self.assertEqual(handler, expected) - - def test_match_type_no_match(self): - param = ArrayParameter(Array(str)) - values = [ - ['a', 1, 'c'], - ('a', 'b', 'c'), - 'spam', - ] - for value in values: - with self.subTest(value): - handler = param.match_type(value) - - self.assertIs(handler, None) - - def test_coerce_simple(self): - param = ArrayParameter(Array(str)) - values = [ - ['a', 'b', 'c'], - [], - ] - for value in values: - with self.subTest(value): - handler = param.match_type(value) - coerced = handler.coerce(value) - - self.assertEqual(coerced, value) - - def test_coerce_complicated(self): - param = ArrayParameter(Array(Union(str, Basic))) - value = [ - 'a', - BASIC_FULL, - 'c', - ] - handler = param.match_type(value) - coerced = handler.coerce(value) - - self.assertEqual(coerced, [ - 'a', - Basic(name='spam', value='eggs'), - 'c', - ]) - - def test_validate(self): - param = ArrayParameter(Array(str)) - handler = param.match_type(['a', 'b', 'c']) - handler.validate(['a', 'b', 'c']) - - def test_as_data_simple(self): - param = ArrayParameter(Array(str)) - handler = param.match_type(['a', 'b', 'c']) - data = handler.as_data(['a', 'b', 'c']) - - self.assertEqual(data, ['a', 'b', 'c']) - - def test_as_data_complicated(self): - param = ArrayParameter(Array(Union(str, Basic))) - value = [ - 'a', - BASIC_FULL, - 'c', - ] - handler = param.match_type(value) - data = handler.as_data([ - 'a', - Basic(name='spam', value='eggs'), - 'c', - ]) - - self.assertEqual(data, value) - - -class MappingParameterTests(unittest.TestCase): - - def test_match_type_match(self): - param = MappingParameter(Mapping(int)) - expected = MappingParameter.HANDLER(Mapping(int)) - values = [ - {'a': 1, 'b': 2, 'c': 3}, - {}, - ] - for value in values: - with self.subTest(value): - handler = param.match_type(value) - - self.assertEqual(handler, expected) - - def test_match_type_no_match(self): - param = MappingParameter(Mapping(int)) - values = [ - {'a': 1, 'b': '2', 'c': 3}, - [('a', 1), ('b', 2), ('c', 3)], - 'spam', - ] - for value in values: - with self.subTest(value): - handler = param.match_type(value) - - self.assertIs(handler, None) - - def test_coerce_simple(self): - param = MappingParameter(Mapping(int)) - values = [ - {'a': 1, 'b': 2, 'c': 3}, - {}, - ] - for value in values: - with self.subTest(value): - handler = param.match_type(value) - coerced = handler.coerce(value) - - self.assertEqual(coerced, value) - - def test_coerce_complicated(self): - param = MappingParameter(Mapping(Union(int, Basic))) - value = { - 'a': 1, - 'b': BASIC_FULL, - 'c': 3, - } - handler = param.match_type(value) - coerced = handler.coerce(value) - - self.assertEqual(coerced, { - 'a': 1, - 'b': Basic(name='spam', value='eggs'), - 'c': 3, - }) - - def test_validate(self): - raw = {'a': 1, 'b': 2, 'c': 3} - param = MappingParameter(Mapping(int)) - handler = param.match_type(raw) - handler.validate(raw) - - def test_as_data_simple(self): - raw = {'a': 1, 'b': 2, 'c': 3} - param = MappingParameter(Mapping(int)) - handler = param.match_type(raw) - data = handler.as_data(raw) - - self.assertEqual(data, raw) - - def test_as_data_complicated(self): - param = MappingParameter(Mapping(Union(int, Basic))) - value = { - 'a': 1, - 'b': BASIC_FULL, - 'c': 3, - } - handler = param.match_type(value) - data = handler.as_data({ - 'a': 1, - 'b': Basic(name='spam', value='eggs'), - 'c': 3, - }) - - self.assertEqual(data, value) - - -class ComplexParameterTests(unittest.TestCase): - - def test_match_type_none_missing(self): - fields = Fields(*FIELDS_BASIC) - param = ComplexParameter(fields) - handler = param.match_type(BASIC_FULL) - - self.assertIs(type(handler), ComplexParameter.HANDLER) - self.assertEqual(handler.datatype.FIELDS, fields) - - def test_match_type_missing_optional(self): - fields = Fields( - Field('name'), - Field.START_OPTIONAL, - Field('value'), - ) - param = ComplexParameter(fields) - handler = param.match_type({'name': 'spam'}) - - self.assertIs(type(handler), ComplexParameter.HANDLER) - self.assertEqual(handler.datatype.FIELDS, fields) - self.assertNotIn('value', handler.handlers) - - def test_coerce(self): - handler = ComplexParameter.HANDLER(Basic) - coerced = handler.coerce(BASIC_FULL) - - self.assertEqual(coerced, Basic(**BASIC_FULL)) - - def test_validate(self): - handler = ComplexParameter.HANDLER(Basic) - handler.coerce(BASIC_FULL) - coerced = Basic(**BASIC_FULL) - handler.validate(coerced) - - def test_as_data(self): - handler = ComplexParameter.HANDLER(Basic) - handler.coerce(BASIC_FULL) - coerced = Basic(**BASIC_FULL) - data = handler.as_data(coerced) - - self.assertEqual(data, BASIC_FULL) diff --git a/tests/debugger_protocol/messages/__init__.py b/tests/debugger_protocol/messages/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/debugger_protocol/messages/test_events.py b/tests/debugger_protocol/messages/test_events.py deleted file mode 100644 index 9c623132..00000000 --- a/tests/debugger_protocol/messages/test_events.py +++ /dev/null @@ -1,356 +0,0 @@ -import unittest - -from debugger_protocol.messages import events - - -class StringLike: - - def __init__(self, value): - self.value = value - - def __str__(self): - return self.value - - -class EventsTests(unittest.TestCase): - - def test_implicit___all__(self): - names = set(name - for name in vars(events) - if not name.startswith('__')) - - self.assertEqual(names, { - 'InitializedEvent', - 'StoppedEvent', - 'ContinuedEvent', - 'ExitedEvent', - 'TerminatedEvent', - 'ThreadEvent', - 'OutputEvent', - 'BreakpointEvent', - 'ModuleEvent', - 'LoadedSourceEvent', - 'ProcessEvent', - }) - - -class TestBase: - - NAME = None - EVENT = None - BODY = None - BODY_MIN = None - - def test_event_full(self): - event = self.EVENT(self.BODY, seq=9) - - self.assertEqual(event.event, self.NAME) - self.assertEqual(event.body, self.BODY) - - def test_event_minimal(self): - event = self.EVENT(self.BODY_MIN, seq=9) - - self.assertEqual(event.body, self.BODY_MIN) - - def test_event_empty_body(self): - if self.BODY_MIN: - with self.assertRaises(TypeError): - self.EVENT({}, seq=9) - - def test_from_data(self): - event = self.EVENT.from_data( - type='event', - seq=9, - event=self.NAME, - body=self.BODY, - ) - - self.assertEqual(event.body, self.BODY) - - def test_as_data(self): - event = self.EVENT(self.BODY, seq=9) - data = event.as_data() - - self.assertEqual(data, { - 'type': 'event', - 'seq': 9, - 'event': self.NAME, - 'body': self.BODY, - }) - - -class InitializedEventTests(unittest.TestCase): - - def test_event(self): - event = events.InitializedEvent(seq=9) - - self.assertEqual(event.event, 'initialized') - - -class StoppedEventTests(TestBase, unittest.TestCase): - - NAME = 'stopped' - EVENT = events.StoppedEvent - BODY = { - 'reason': 'step', - 'description': 'descr', - 'threadId': 10, - 'text': '...', - 'allThreadsStopped': False, - } - BODY_MIN = { - 'reason': 'step', - } - - def test_reasons(self): - for reason in events.StoppedEvent.BODY.REASONS: - with self.subTest(reason): - body = { - 'reason': reason, - } - event = events.StoppedEvent(body, seq=9) - - self.assertEqual(event.body.reason, reason) - - -class ContinuedEventTests(TestBase, unittest.TestCase): - - NAME = 'continued' - EVENT = events.ContinuedEvent - BODY = { - 'threadId': 10, - 'allThreadsContinued': True, - } - BODY_MIN = { - 'threadId': 10, - } - - -class ExitedEventTests(TestBase, unittest.TestCase): - - NAME = 'exited' - EVENT = events.ExitedEvent - BODY = { - 'exitCode': 0, - } - BODY_MIN = BODY - - -class TerminatedEventTests(TestBase, unittest.TestCase): - - NAME = 'terminated' - EVENT = events.TerminatedEvent - BODY = { - 'restart': True, - } - BODY_MIN = {} - - -class ThreadEventTests(TestBase, unittest.TestCase): - - NAME = 'thread' - EVENT = events.ThreadEvent - BODY = { - 'threadId': 10, - 'reason': 'exited', - } - BODY_MIN = BODY - - def test_reasons(self): - for reason in self.EVENT.BODY.REASONS: - with self.subTest(reason): - body = { - 'threadId': 10, - 'reason': reason, - } - event = self.EVENT(body, seq=9) - - self.assertEqual(event.body.reason, reason) - - -class OutputEventTests(TestBase, unittest.TestCase): - - NAME = 'output' - EVENT = events.OutputEvent - BODY = { - 'output': '...', - 'category': 'stdout', - 'variablesReference': 10, - 'source': '...', - 'line': 11, - 'column': 12, - 'data': None, - } - BODY_MIN = { - 'output': '...', - } - - def test_categories(self): - for category in self.EVENT.BODY.CATEGORIES: - with self.subTest(category): - body = dict(self.BODY, **{ - 'category': category, - }) - event = self.EVENT(body, seq=9) - - self.assertEqual(event.body.category, category) - - -class BreakpointEventTests(TestBase, unittest.TestCase): - - NAME = 'breakpoint' - EVENT = events.BreakpointEvent - BODY = { - 'breakpoint': { - 'id': 10, - 'verified': True, - 'message': '...', - 'source': { - 'name': '...', - 'path': '...', - 'sourceReference': 15, - 'presentationHint': 'normal', - 'origin': '...', - 'sources': [ - {'name': '...'}, - ], - 'adapterData': None, - 'checksums': [ - {'algorithm': 'MD5', 'checksum': '...'}, - ], - }, - 'line': 11, - 'column': 12, - 'endLine': 11, - 'endColumn': 12, - }, - 'reason': 'new', - } - BODY_MIN = { - 'breakpoint': { - 'id': 10, - 'verified': True, - }, - 'reason': 'new', - } - - def test_reasons(self): - for reason in self.EVENT.BODY.REASONS: - with self.subTest(reason): - body = dict(self.BODY, **{ - 'reason': reason, - }) - event = self.EVENT(body, seq=9) - - self.assertEqual(event.body.reason, reason) - - -class ModuleEventTests(TestBase, unittest.TestCase): - - NAME = 'module' - EVENT = events.ModuleEvent - BODY = { - 'module': { - 'id': 10, - 'name': '...', - 'path': '...', - 'isOptimized': False, - 'isUserCode': True, - 'version': '...', - 'symbolStatus': '...', - 'symbolFilePath': '...', - 'dateTimeStamp': '...', - 'addressRange': '...', - }, - 'reason': 'new', - } - BODY_MIN = { - 'module': { - 'id': 10, - 'name': '...', - }, - 'reason': 'new', - } - - def test_reasons(self): - for reason in self.EVENT.BODY.REASONS: - with self.subTest(reason): - body = dict(self.BODY, **{ - 'reason': reason, - }) - event = self.EVENT(body, seq=9) - - self.assertEqual(event.body.reason, reason) - - -class LoadedSourceEventTests(TestBase, unittest.TestCase): - - NAME = 'loadedSource' - EVENT = events.LoadedSourceEvent - BODY = { - 'source': { - 'name': '...', - 'path': '...', - 'sourceReference': 15, - 'presentationHint': 'normal', - 'origin': '...', - 'sources': [ - {'name': '...'}, - ], - 'adapterData': None, - 'checksums': [ - {'algorithm': 'MD5', 'checksum': '...'}, - ], - }, - 'reason': 'new', - } - BODY_MIN = { - 'source': {}, - 'reason': 'new', - } - - def test_reasons(self): - for reason in self.EVENT.BODY.REASONS: - with self.subTest(reason): - body = dict(self.BODY, **{ - 'reason': reason, - }) - event = self.EVENT(body, seq=9) - - self.assertEqual(event.body.reason, reason) - - def test_hints(self): - for hint in self.EVENT.BODY.FIELDS[0].datatype.HINTS: - with self.subTest(hint): - body = dict(self.BODY) - body['source'].update(**{ - 'presentationHint': hint, - }) - event = self.EVENT(body, seq=9) - - self.assertEqual(event.body.source.presentationHint, hint) - - -class ProcessEventTests(TestBase, unittest.TestCase): - - NAME = 'process' - EVENT = events.ProcessEvent - BODY = { - 'name': '...', - 'systemProcessId': 10, - 'isLocalProcess': True, - 'startMethod': 'launch', - } - BODY_MIN = { - 'name': '...', - } - - def test_start_methods(self): - for method in self.EVENT.BODY.START_METHODS: - with self.subTest(method): - body = dict(self.BODY, **{ - 'startMethod': method, - }) - event = self.EVENT(body, seq=9) - - self.assertEqual(event.body.startMethod, method) diff --git a/tests/debugger_protocol/messages/test_message.py b/tests/debugger_protocol/messages/test_message.py deleted file mode 100644 index 0171cde0..00000000 --- a/tests/debugger_protocol/messages/test_message.py +++ /dev/null @@ -1,936 +0,0 @@ -import unittest - -from debugger_protocol.arg import FieldsNamespace, Field -from debugger_protocol.messages import register -from debugger_protocol.messages.message import ( - ProtocolMessage, Request, Response, Event) - - -@register -class DummyRequest(object): - TYPE = 'request' - TYPE_KEY = 'command' - COMMAND = '...' - - -@register -class DummyResponse(object): - TYPE = 'response' - TYPE_KEY = 'command' - COMMAND = '...' - - -@register -class DummyEvent(object): - TYPE = 'event' - TYPE_KEY = 'event' - EVENT = '...' - - -class FakeMsg(ProtocolMessage): - - SEQ = 0 - - @classmethod - def _next_reqid(cls): - return cls.SEQ - - -class ProtocolMessageTests(unittest.TestCase): - - def test_from_data(self): - data = { - 'type': 'event', - 'seq': 10, - } - msg = ProtocolMessage.from_data(**data) - - self.assertEqual(msg.type, 'event') - self.assertEqual(msg.seq, 10) - - def test_defaults(self): # no args - class Spam(FakeMsg): - SEQ = 10 - TYPE = 'event' - - msg = Spam() - - self.assertEqual(msg.type, 'event') - self.assertEqual(msg.seq, 10) - - def test_all_args(self): - msg = ProtocolMessage(10, type='event') - - self.assertEqual(msg.type, 'event') - self.assertEqual(msg.seq, 10) - - def test_coercion_seq(self): - msg = ProtocolMessage('10', type='event') - - self.assertEqual(msg.seq, 10) - - def test_validation(self): - # type - - with self.assertRaises(TypeError): - ProtocolMessage(type=None) - with self.assertRaises(ValueError): - ProtocolMessage(type='spam') - - class Other(ProtocolMessage): - TYPE = 'spam' - - with self.assertRaises(ValueError): - Other(type='event') - - # seq - - with self.assertRaises(TypeError): - ProtocolMessage(None, type='event') - with self.assertRaises(ValueError): - ProtocolMessage(-1, type='event') - - def test_readonly(self): - msg = ProtocolMessage(10, type='event') - - with self.assertRaises(AttributeError): - msg.seq = 11 - with self.assertRaises(AttributeError): - msg.type = 'event' - with self.assertRaises(AttributeError): - msg.spam = object() - with self.assertRaises(AttributeError): - del msg.seq - - def test_repr(self): - msg = ProtocolMessage(10, type='event') - result = repr(msg) - - self.assertEqual(result, "ProtocolMessage(type='event', seq=10)") - - def test_repr_subclass(self): - class Eventish(ProtocolMessage): - TYPE = 'event' - - msg = Eventish(10) - result = repr(msg) - - self.assertEqual(result, 'Eventish(seq=10)') - - def test_as_data(self): - msg = ProtocolMessage(10, type='event') - data = msg.as_data() - - self.assertEqual(data, { - 'type': 'event', - 'seq': 10, - }) - - -class RequestTests(unittest.TestCase): - - def test_from_data_without_arguments(self): - data = { - 'type': 'request', - 'seq': 10, - 'command': 'spam', - } - msg = Request.from_data(**data) - - self.assertEqual(msg.type, 'request') - self.assertEqual(msg.seq, 10) - self.assertEqual(msg.command, 'spam') - self.assertIsNone(msg.arguments) - - def test_from_data_with_arguments(self): - class Spam(Request): - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('a'), - ] - - data = { - 'type': 'request', - 'seq': 10, - 'command': 'spam', - 'arguments': {'a': 'b'}, - } - #msg = Request.from_data(**data) - msg = Spam.from_data(**data) - - self.assertEqual(msg.type, 'request') - self.assertEqual(msg.seq, 10) - self.assertEqual(msg.command, 'spam') - self.assertEqual(msg.arguments, {'a': 'b'}) - - def test_defaults(self): - class Spam(Request, FakeMsg): - SEQ = 10 - COMMAND = 'spam' - - msg = Spam() - - self.assertEqual(msg.type, 'request') - self.assertEqual(msg.seq, 10) - self.assertEqual(msg.command, 'spam') - self.assertIsNone(msg.arguments) - - def test_all_args(self): - class Spam(Request): - class ARGUMENTS(FieldsNamespace): - FIELDS = [ - Field('a'), - ] - - args = {'a': 'b'} - msg = Spam(arguments=args, command='spam', seq=10) - - self.assertEqual(msg.type, 'request') - self.assertEqual(msg.seq, 10) - self.assertEqual(msg.command, 'spam') - self.assertEqual(msg.arguments, args) - - def test_no_arguments_not_required(self): - class Spam(Request): - COMMAND = 'spam' - ARGUMENTS = True - ARGUMENTS_REQUIRED = False - - msg = Spam() - - self.assertIsNone(msg.arguments) - - def test_no_args(self): - with self.assertRaises(TypeError): - Request() - - def test_coercion_arguments(self): - class Spam(Request): - COMMAND = 'spam' - class ARGUMENTS(FieldsNamespace): # noqa - FIELDS = [ - Field('a'), - ] - - args = [('a', 'b')] - msg = Spam(args) - - self.assertEqual(msg.arguments, {'a': 'b'}) - - with self.assertRaises(TypeError): - Spam(command='spam', arguments=11) - - def test_validation(self): - with self.assertRaises(TypeError): - Request() - - # command - - class Other1(Request): - COMMAND = 'eggs' - - with self.assertRaises(ValueError): - # command doesn't match - Other1(arguments=10, command='spam') - - # arguments - - with self.assertRaises(TypeError): - # unexpected arguments - Request(arguments=10, command='spam') - - class Other2(Request): - COMMAND = 'spam' - ARGUMENTS = int - - with self.assertRaises(ValueError): - # missing arguments (implicitly required) - Other2(command='eggs') - - class Other3(Request): - COMMAND = 'eggs' - ARGUMENTS = int - ARGUMENTS_REQUIRED = True - - with self.assertRaises(ValueError): - # missing arguments (explicitly required) - Other2(command='eggs') - - def test_repr_minimal(self): - msg = Request(command='spam', seq=10) - result = repr(msg) - - self.assertEqual(result, "Request(command='spam', seq=10)") - - def test_repr_full(self): - msg = Request(command='spam', seq=10) - result = repr(msg) - - self.assertEqual(result, "Request(command='spam', seq=10)") - - def test_repr_subclass_minimal(self): - class SpamRequest(Request): - COMMAND = 'spam' - - msg = SpamRequest(seq=10) - result = repr(msg) - - self.assertEqual(result, "SpamRequest(seq=10)") - - def test_repr_subclass_full(self): - class SpamRequest(Request): - COMMAND = 'spam' - class ARGUMENTS(FieldsNamespace): # noqa - FIELDS = [ - Field('a'), - ] - - msg = SpamRequest(arguments={'a': 'b'}, seq=10) - result = repr(msg) - - self.assertEqual(result, - "SpamRequest(arguments=ARGUMENTS(a='b'), seq=10)") - - def test_as_data_minimal(self): - msg = Request(command='spam', seq=10) - data = msg.as_data() - - self.assertEqual(data, { - 'type': 'request', - 'seq': 10, - 'command': 'spam', - }) - - def test_as_data_full(self): - class Spam(Request): - COMMAND = 'spam' - class ARGUMENTS(FieldsNamespace): # noqa - FIELDS = [ - Field('a'), - ] - - msg = Spam(arguments={'a': 'b'}, seq=10) - data = msg.as_data() - - self.assertEqual(data, { - 'type': 'request', - 'seq': 10, - 'command': 'spam', - 'arguments': {'a': 'b'}, - }) - - -class ResponseTests(unittest.TestCase): - - def test_from_data_without_body(self): - data = { - 'type': 'response', - 'seq': 10, - 'command': 'spam', - 'request_seq': 9, - 'success': True, - } - msg = Response.from_data(**data) - - self.assertEqual(msg.type, 'response') - self.assertEqual(msg.seq, 10) - self.assertEqual(msg.command, 'spam') - self.assertEqual(msg.request_seq, 9) - self.assertTrue(msg.success) - self.assertIsNone(msg.body) - self.assertIsNone(msg.message) - - def test_from_data_with_body(self): - class Spam(Response): - class BODY(FieldsNamespace): - FIELDS = [ - Field('a'), - ] - - data = { - 'type': 'response', - 'seq': 10, - 'command': 'spam', - 'request_seq': 9, - 'success': True, - 'body': {'a': 'b'}, - } - msg = Spam.from_data(**data) - - self.assertEqual(msg.type, 'response') - self.assertEqual(msg.seq, 10) - self.assertEqual(msg.command, 'spam') - self.assertEqual(msg.request_seq, 9) - self.assertTrue(msg.success) - self.assertEqual(msg.body, {'a': 'b'}) - self.assertIsNone(msg.message) - - def test_from_data_error_without_body(self): - data = { - 'type': 'response', - 'seq': 10, - 'command': 'spam', - 'request_seq': 9, - 'success': False, - 'message': 'oops!', - } - msg = Response.from_data(**data) - - self.assertEqual(msg.type, 'response') - self.assertEqual(msg.seq, 10) - self.assertEqual(msg.command, 'spam') - self.assertEqual(msg.request_seq, 9) - self.assertFalse(msg.success) - self.assertIsNone(msg.body) - self.assertEqual(msg.message, 'oops!') - - def test_from_data_error_with_body(self): - class Spam(Response): - class ERROR_BODY(FieldsNamespace): - FIELDS = [ - Field('a'), - ] - - data = { - 'type': 'response', - 'seq': 10, - 'command': 'spam', - 'request_seq': 9, - 'success': False, - 'message': 'oops!', - 'body': {'a': 'b'}, - } - msg = Spam.from_data(**data) - - self.assertEqual(msg.type, 'response') - self.assertEqual(msg.seq, 10) - self.assertEqual(msg.command, 'spam') - self.assertEqual(msg.request_seq, 9) - self.assertFalse(msg.success) - self.assertEqual(msg.body, {'a': 'b'}) - self.assertEqual(msg.message, 'oops!') - - def test_defaults(self): - class Spam(Response, FakeMsg): - SEQ = 10 - COMMAND = 'spam' - - msg = Spam('9') - - self.assertEqual(msg.type, 'response') - self.assertEqual(msg.seq, 10) - self.assertEqual(msg.request_seq, 9) - self.assertEqual(msg.command, 'spam') - self.assertTrue(msg.success) - self.assertIsNone(msg.body) - self.assertIsNone(msg.message) - - def test_all_args_not_error(self): - class Spam(Response): - class BODY(FieldsNamespace): - FIELDS = [ - Field('a'), - ] - - msg = Spam('9', command='spam', success=True, body={'a': 'b'}, - seq=10, type='response') - - self.assertEqual(msg.type, 'response') - self.assertEqual(msg.seq, 10) - self.assertEqual(msg.request_seq, 9) - self.assertEqual(msg.command, 'spam') - self.assertTrue(msg.success) - self.assertEqual(msg.body, {'a': 'b'}) - self.assertIsNone(msg.message) - - def test_all_args_error(self): - class Spam(Response): - COMMAND = 'spam' - class ERROR_BODY(FieldsNamespace): # noqa - FIELDS = [ - Field('a'), - ] - - msg = Spam('9', success=False, message='oops!', body={'a': 'b'}, - seq=10, type='response') - - self.assertEqual(msg.type, 'response') - self.assertEqual(msg.seq, 10) - self.assertEqual(msg.command, 'spam') - self.assertEqual(msg.request_seq, 9) - self.assertFalse(msg.success) - self.assertEqual(msg.body, Spam.ERROR_BODY(a='b')) - self.assertEqual(msg.message, 'oops!') - - def test_no_body_not_required(self): - class Spam(Response): - COMMAND = 'spam' - BODY = True - BODY_REQUIRED = False - - msg = Spam('9') - - self.assertIsNone(msg.body) - - def test_no_error_body_not_required(self): - class Spam(Response): - COMMAND = 'spam' - ERROR_BODY = True - ERROR_BODY_REQUIRED = False - - msg = Spam('9', success=False, message='oops!') - - self.assertIsNone(msg.body) - - def test_no_args(self): - with self.assertRaises(TypeError): - Response() - - def test_coercion_request_seq(self): - msg = Response('9', command='spam') - - self.assertEqual(msg.request_seq, 9) - - def test_coercion_success(self): - msg1 = Response(9, success=1, command='spam') - msg2 = Response(9, success=None, command='spam', message='oops!') - - self.assertIs(msg1.success, True) - self.assertIs(msg2.success, False) - - def test_coercion_body(self): - class Spam(Response): - COMMAND = 'spam' - class BODY(FieldsNamespace): # noqa - FIELDS = [ - Field('a'), - ] - - body = [('a', 'b')] - msg = Spam(9, body=body) - - self.assertEqual(msg.body, {'a': 'b'}) - - with self.assertRaises(TypeError): - Spam(9, command='spam', body=11) - - def test_coercion_error_body(self): - class Spam(Response): - COMMAND = 'spam' - class ERROR_BODY(FieldsNamespace): # noqa - FIELDS = [ - Field('a'), - ] - - body = [('a', 'b')] - msg = Spam(9, body=body, success=False, message='oops!') - - self.assertEqual(msg.body, {'a': 'b'}) - - with self.assertRaises(TypeError): - Spam(9, command='spam', success=False, message='oops!', body=11) - - def test_validation(self): - # request_seq - - with self.assertRaises(TypeError): - # missing - Response(None, command='spam') - with self.assertRaises(TypeError): - # missing - Response('', command='spam') - with self.assertRaises(TypeError): - # couldn't convert to int - Response(object(), command='spam') - with self.assertRaises(ValueError): - # not non-negative - Response(-1, command='spam') - - # command - - with self.assertRaises(TypeError): - # missing - Response(9, command=None) - with self.assertRaises(TypeError): - # missing - Response(9, command='') - - class Other1(Response): - COMMAND = 'eggs' - - with self.assertRaises(ValueError): - # does not match - Other1(9, command='spam') - - # body - - class Other2(Response): - class BODY(FieldsNamespace): - FIELDS = [ - Field('a'), - ] - - ERROR_BODY = BODY - - with self.assertRaises(ValueError): - # unexpected - Response(9, command='spam', body=11) - with self.assertRaises(TypeError): - # missing (implicitly required) - Other2(9, command='spam') - with self.assertRaises(TypeError): - # missing (explicitly required) - Other2.BODY_REQUIRED = True - Other2(9, command='spam') - with self.assertRaises(ValueError): - # unexpected (error) - Response(9, command='spam', body=11, success=False, message=':(') - with self.assertRaises(TypeError): - # missing (error) (implicitly required) - Other2(9, command='spam', success=False, message=':(') - with self.assertRaises(TypeError): - # missing (error) (explicitly required) - Other2.ERROR_BODY_REQUIRED = True - Other2(9, command='spam', success=False, message=':(') - - # message - - with self.assertRaises(TypeError): - # missing - Response(9, command='spam', success=False) - - def test_repr_minimal(self): - msg = Response(9, command='spam', seq=10) - result = repr(msg) - - self.assertEqual(result, - "Response(command='spam', request_seq=9, success=True, seq=10)") # noqa - - def test_repr_full(self): - msg = Response(9, command='spam', seq=10) - result = repr(msg) - - self.assertEqual(result, - "Response(command='spam', request_seq=9, success=True, seq=10)") # noqa - - def test_repr_error_minimal(self): - msg = Response(9, command='spam', success=False, message='oops!', - seq=10) - result = repr(msg) - - self.assertEqual(result, - "Response(command='spam', request_seq=9, success=False, message='oops!', seq=10)") # noqa - - def test_repr_error_full(self): - msg = Response(9, command='spam', success=False, message='oops!', - seq=10) - result = repr(msg) - - self.assertEqual(result, - "Response(command='spam', request_seq=9, success=False, message='oops!', seq=10)") # noqa - - def test_repr_subclass_minimal(self): - class SpamResponse(Response): - COMMAND = 'spam' - - msg = SpamResponse(9, seq=10) - result = repr(msg) - - self.assertEqual(result, - "SpamResponse(request_seq=9, success=True, seq=10)") - - def test_repr_subclass_full(self): - class SpamResponse(Response): - COMMAND = 'spam' - class BODY(FieldsNamespace): # noqa - FIELDS = [ - Field('a'), - ] - - msg = SpamResponse(9, body={'a': 'b'}, seq=10) - result = repr(msg) - - self.assertEqual(result, - "SpamResponse(request_seq=9, success=True, body=BODY(a='b'), seq=10)") # noqa - - def test_repr_subclass_error_minimal(self): - class SpamResponse(Response): - COMMAND = 'spam' - - msg = SpamResponse(9, success=False, message='oops!', seq=10) - result = repr(msg) - - self.assertEqual(result, - "SpamResponse(request_seq=9, success=False, message='oops!', seq=10)") # noqa - - def test_repr_subclass_error_full(self): - class SpamResponse(Response): - COMMAND = 'spam' - class ERROR_BODY(FieldsNamespace): # noqa - FIELDS = [ - Field('a'), - ] - - msg = SpamResponse(9, success=False, message='oops!', body={'a': 'b'}, - seq=10) - result = repr(msg) - - self.assertEqual(result, - "SpamResponse(request_seq=9, success=False, message='oops!', body=ERROR_BODY(a='b'), seq=10)") # noqa - - def test_as_data_minimal(self): - msg = Response(9, command='spam', seq=10) - data = msg.as_data() - - self.assertEqual(data, { - 'type': 'response', - 'seq': 10, - 'request_seq': 9, - 'command': 'spam', - 'success': True, - }) - - def test_as_data_full(self): - class Spam(Response): - COMMAND = 'spam' - class BODY(FieldsNamespace): # noqa - FIELDS = [ - Field('a'), - ] - - msg = Spam(9, body={'a': 'b'}, seq=10) - data = msg.as_data() - - self.assertEqual(data, { - 'type': 'response', - 'seq': 10, - 'request_seq': 9, - 'command': 'spam', - 'success': True, - 'body': {'a': 'b'}, - }) - - def test_as_data_error_minimal(self): - msg = Response(9, command='spam', success=False, message='oops!', - seq=10) - data = msg.as_data() - - self.assertEqual(data, { - 'type': 'response', - 'seq': 10, - 'request_seq': 9, - 'command': 'spam', - 'success': False, - 'message': 'oops!', - }) - - def test_as_data_error_full(self): - class Spam(Response): - COMMAND = 'spam' - class ERROR_BODY(FieldsNamespace): # noqa - FIELDS = [ - Field('a'), - ] - - msg = Spam(9, success=False, body={'a': 'b'}, message='oops!', seq=10) - data = msg.as_data() - - self.assertEqual(data, { - 'type': 'response', - 'seq': 10, - 'request_seq': 9, - 'command': 'spam', - 'success': False, - 'message': 'oops!', - 'body': {'a': 'b'}, - }) - - -class EventTests(unittest.TestCase): - - def test_from_data_without_body(self): - data = { - 'type': 'event', - 'seq': 10, - 'event': 'spam', - } - msg = Event.from_data(**data) - - self.assertEqual(msg.type, 'event') - self.assertEqual(msg.seq, 10) - self.assertEqual(msg.event, 'spam') - self.assertIsNone(msg.body) - - def test_from_data_with_body(self): - class Spam(Event): - class BODY(FieldsNamespace): - FIELDS = [ - Field('a'), - ] - - data = { - 'type': 'event', - 'seq': 10, - 'event': 'spam', - 'body': {'a': 'b'}, - } - msg = Spam.from_data(**data) - - self.assertEqual(msg.type, 'event') - self.assertEqual(msg.seq, 10) - self.assertEqual(msg.event, 'spam') - self.assertEqual(msg.body, {'a': 'b'}) - - def test_defaults(self): # no args - class Spam(Event, FakeMsg): - SEQ = 10 - EVENT = 'spam' - - msg = Spam() - - self.assertEqual(msg.type, 'event') - self.assertEqual(msg.seq, 10) - self.assertEqual(msg.event, 'spam') - self.assertIsNone(msg.body) - - def test_all_args(self): - class Spam(Event): - class BODY(FieldsNamespace): - FIELDS = [ - Field('a'), - ] - - msg = Spam(event='spam', body={'a': 'b'}, seq=10, type='event') - - self.assertEqual(msg.type, 'event') - self.assertEqual(msg.seq, 10) - self.assertEqual(msg.event, 'spam') - self.assertEqual(msg.body, {'a': 'b'}) - - def test_no_body_not_required(self): - class Spam(Event): - EVENT = 'spam' - BODY = True - BODY_REQUIRED = False - - msg = Spam() - - self.assertIsNone(msg.body) - - def test_no_args(self): - with self.assertRaises(TypeError): - Event() - - def test_coercion_body(self): - class Spam(Event): - EVENT = 'spam' - class BODY(FieldsNamespace): # noqa - FIELDS = [ - Field('a'), - ] - - body = [('a', 'b')] - msg = Spam(body=body) - - self.assertEqual(msg.body, {'a': 'b'}) - - with self.assertRaises(TypeError): - Spam(event='spam', body=11) - - def test_validation(self): - # event - - with self.assertRaises(TypeError): - # missing - Event(event=None) - with self.assertRaises(TypeError): - # missing - Event(event='') - - class Other1(Event): - EVENT = 'eggs' - - with self.assertRaises(ValueError): - # does not match - Other1(event='spam') - - # body - - class Other2(Event): - class BODY(FieldsNamespace): - FIELDS = [ - Field('a'), - ] - - with self.assertRaises(ValueError): - # unexpected - Event(event='spam', body=11) - with self.assertRaises(TypeError): - # missing (implicitly required) - Other2(9, command='spam') - with self.assertRaises(TypeError): - # missing (explicitly required) - Other2.BODY_REQUIRED = True - Other2(9, command='spam') - - def test_repr_minimal(self): - msg = Event(event='spam', seq=10) - result = repr(msg) - - self.assertEqual(result, "Event(event='spam', seq=10)") - - def test_repr_full(self): - msg = Event(event='spam', seq=10) - result = repr(msg) - - self.assertEqual(result, "Event(event='spam', seq=10)") - - def test_repr_subclass_minimal(self): - class SpamEvent(Event): - EVENT = 'spam' - - msg = SpamEvent(seq=10) - result = repr(msg) - - self.assertEqual(result, 'SpamEvent(seq=10)') - - def test_repr_subclass_full(self): - class SpamEvent(Event): - EVENT = 'spam' - class BODY(FieldsNamespace): # noqa - FIELDS = [ - Field('a'), - ] - - msg = SpamEvent(body={'a': 'b'}, seq=10) - result = repr(msg) - - self.assertEqual(result, "SpamEvent(body=BODY(a='b'), seq=10)") - - def test_as_data_minimal(self): - msg = Event(event='spam', seq=10) - data = msg.as_data() - - self.assertEqual(data, { - 'type': 'event', - 'seq': 10, - 'event': 'spam', - }) - - def test_as_data_full(self): - class Spam(Event): - EVENT = 'spam' - class BODY(FieldsNamespace): # noqa - FIELDS = [ - Field('a'), - ] - - msg = Spam(body={'a': 'b'}, seq=10) - data = msg.as_data() - - self.assertEqual(data, { - 'type': 'event', - 'seq': 10, - 'event': 'spam', - 'body': {'a': 'b'}, - }) diff --git a/tests/debugger_protocol/messages/test_requests.py b/tests/debugger_protocol/messages/test_requests.py deleted file mode 100644 index 87a87386..00000000 --- a/tests/debugger_protocol/messages/test_requests.py +++ /dev/null @@ -1,126 +0,0 @@ -import unittest - -from debugger_protocol.messages import requests - - -class RequestsTests(unittest.TestCase): - - def test_implicit___all__(self): - names = set(name - for name in vars(requests) - if not name.startswith('__')) - - self.assertEqual(names, { - 'ErrorResponse', - 'RunInTerminalRequest', - 'RunInTerminalResponse', - 'InitializeRequest', - 'InitializeResponse', - 'ConfigurationDoneRequest', - 'ConfigurationDoneResponse', - 'LaunchRequest', - 'LaunchResponse', - 'AttachRequest', - 'AttachResponse', - 'RestartRequest', - 'RestartResponse', - 'DisconnectRequest', - 'DisconnectResponse', - 'SetBreakpointsRequest', - 'SetBreakpointsResponse', - 'SetFunctionBreakpointsRequest', - 'SetFunctionBreakpointsResponse', - 'SetExceptionBreakpointsRequest', - 'SetExceptionBreakpointsResponse', - 'ContinueRequest', - 'ContinueResponse', - 'NextRequest', - 'NextResponse', - 'StepInRequest', - 'StepInResponse', - 'StepOutRequest', - 'StepOutResponse', - 'StepBackRequest', - 'StepBackResponse', - 'ReverseContinueRequest', - 'ReverseContinueResponse', - 'RestartFrameRequest', - 'RestartFrameResponse', - 'GotoRequest', - 'GotoResponse', - 'PauseRequest', - 'PauseResponse', - 'StackTraceRequest', - 'StackTraceResponse', - 'ScopesRequest', - 'ScopesResponse', - 'VariablesRequest', - 'VariablesResponse', - 'SetVariableRequest', - 'SetVariableResponse', - 'SourceRequest', - 'SourceResponse', - 'ThreadsRequest', - 'ThreadsResponse', - 'ModulesRequest', - 'ModulesResponse', - 'LoadedSourcesRequest', - 'LoadedSourcesResponse', - 'EvaluateRequest', - 'EvaluateResponse', - 'StepInTargetsRequest', - 'StepInTargetsResponse', - 'GotoTargetsRequest', - 'GotoTargetsResponse', - 'CompletionsRequest', - 'CompletionsResponse', - 'ExceptionInfoRequest', - 'ExceptionInfoResponse', - }) - - -# TODO: Add tests for every request/response type. - -#class TestBase: -# -# NAME = None -# EVENT = None -# BODY = None -# BODY_MIN = None -# -# def test_event_full(self): -# event = self.EVENT(self.BODY, seq=9) -# -# self.assertEqual(event.event, self.NAME) -# self.assertEqual(event.body, self.BODY) -# -# def test_event_minimal(self): -# event = self.EVENT(self.BODY_MIN, seq=9) -# -# self.assertEqual(event.body, self.BODY_MIN) -# -# def test_event_empty_body(self): -# if self.BODY_MIN: -# with self.assertRaises(TypeError): -# self.EVENT({}, seq=9) -# -# def test_from_data(self): -# event = self.EVENT.from_data( -# type='event', -# seq=9, -# event=self.NAME, -# body=self.BODY, -# ) -# -# self.assertEqual(event.body, self.BODY) -# -# def test_as_data(self): -# event = self.EVENT(self.BODY, seq=9) -# data = event.as_data() -# -# self.assertEqual(data, { -# 'type': 'event', -# 'seq': 9, -# 'event': self.NAME, -# 'body': self.BODY, -# }) diff --git a/tests/debugger_protocol/schema/__init__.py b/tests/debugger_protocol/schema/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/debugger_protocol/schema/helpers.py b/tests/debugger_protocol/schema/helpers.py deleted file mode 100644 index f0f2bc46..00000000 --- a/tests/debugger_protocol/schema/helpers.py +++ /dev/null @@ -1,20 +0,0 @@ -import urllib.error - - -class StubOpener: - - def __init__(self, *files): - self.files = list(files) - self.calls = [] - - def open(self, *args): - self.calls.append(args) - - file = self.files.pop(0) - if file is None: - if args[0].startswith('http'): - raise urllib.error.HTTPError(args[0], 404, 'Not Found', - None, None) - else: - raise FileNotFoundError - return file diff --git a/tests/debugger_protocol/schema/test___main__.py b/tests/debugger_protocol/schema/test___main__.py deleted file mode 100644 index e5694484..00000000 --- a/tests/debugger_protocol/schema/test___main__.py +++ /dev/null @@ -1,126 +0,0 @@ -import contextlib -import io -import sys -from textwrap import dedent -import unittest - -from .helpers import StubOpener -from debugger_protocol.schema.vendored import FILENAME as VENDORED, METADATA -from debugger_protocol.schema.__main__ import ( - COMMANDS, handle_download, handle_check) - - -class Outfile: - - def __init__(self, initial): - self.written = initial - - def write(self, data): - self.written += data - return len(data) - - def __enter__(self): - return self - - def __exit__(self, *args): - pass - - -class CommandRegistryTests(unittest.TestCase): - - def test_commands(self): - self.assertEqual(set(COMMANDS), { - 'download', - 'check', - }) - - -class internal_redirect_stderr: - """Context manager for temporarily redirecting stderr to another file - """ - - def __init__(self, new_target): - self._new_target = new_target - self._old_targets = [] - - def __enter__(self): - self._old_targets.append(sys.stderr) - sys.stderr = self._new_target - return self._new_target - - def __exit__(self, exctype, excinst, exctb): - sys.stderr = self._old_targets.pop() - - -class HandleDownloadTests(unittest.TestCase): - - def test_default_args(self): - schemafile = io.BytesIO(b'') - outfile = Outfile(b'') - buf = io.BytesIO( - b'[{"sha": "fc2395ca3564fb2afded8d90ddbe38dad1bf86f1"}]') - metafile = Outfile('') - opener = StubOpener(schemafile, outfile, buf, metafile) - - stdout = io.StringIO() - try: - redirect_stderr = contextlib.redirect_stderr - except AttributeError: - redirect_stderr = internal_redirect_stderr - - with contextlib.redirect_stdout(stdout): - with redirect_stderr(stdout): - handle_download( - _open=opener.open, _open_url=opener.open) - metadata = '\n'.join(line - for line in metafile.written.splitlines() - if not line.startswith('downloaded: ')) - - self.assertEqual(outfile.written, b'') - self.assertEqual(metadata, dedent(""" - upstream: https://github.com/Microsoft/vscode-debugadapter-node/raw/master/debugProtocol.json - revision: fc2395ca3564fb2afded8d90ddbe38dad1bf86f1 - checksum: e778c3751f9d0bceaf8d5aa81e2c659f - """).strip()) # noqa - self.assertEqual(stdout.getvalue(), dedent("""\ - downloading the schema file from https://github.com/Microsoft/vscode-debugadapter-node/raw/master/debugProtocol.json... - ...schema file written to {}. - saving the schema metadata... - ...metadata written to {}. - """).format(VENDORED, METADATA)) # noqa - - -class HandleCheckTests(unittest.TestCase): - - def test_default_args(self): - metadata = dedent(""" - upstream: https://github.com/x/y/raw/master/z - revision: fc2395ca3564fb2afded8d90ddbe38dad1bf86f1 - checksum: e778c3751f9d0bceaf8d5aa81e2c659f - downloaded: 2018-01-09 13:10:59 (UTC) - """) - opener = StubOpener( - io.StringIO(metadata), - io.BytesIO(b''), # local - io.StringIO(metadata), - io.BytesIO(b''), # upstream - io.BytesIO( - b'[{"sha": "fc2395ca3564fb2afded8d90ddbe38dad1bf86f1"}]'), - ) - - stdout = io.StringIO() - try: - redirect_stderr = contextlib.redirect_stderr - except AttributeError: - redirect_stderr = internal_redirect_stderr - - with contextlib.redirect_stdout(stdout): - with redirect_stderr(stdout): - handle_check( - _open=opener.open, _open_url=opener.open) - - self.assertEqual(stdout.getvalue(), dedent("""\ - checking local schema file... - comparing with upstream schema file... - schema file okay - """)) diff --git a/tests/debugger_protocol/schema/test_file.py b/tests/debugger_protocol/schema/test_file.py deleted file mode 100644 index bbfc00a6..00000000 --- a/tests/debugger_protocol/schema/test_file.py +++ /dev/null @@ -1,22 +0,0 @@ -import io -import unittest - -from .helpers import StubOpener -from debugger_protocol.schema.file import SchemaFileError, read_schema - - -class ReadSchemaTests(unittest.TestCase): - - def test_success(self): - schemafile = io.BytesIO(b'') - opener = StubOpener(schemafile) - - data = read_schema('schema.json', _open=opener.open) - - self.assertEqual(data, b'') - - def test_file_missing(self): - opener = StubOpener(None) - - with self.assertRaises(SchemaFileError): - read_schema('schema.json', _open=opener.open) diff --git a/tests/debugger_protocol/schema/test_metadata.py b/tests/debugger_protocol/schema/test_metadata.py deleted file mode 100644 index a374fdc5..00000000 --- a/tests/debugger_protocol/schema/test_metadata.py +++ /dev/null @@ -1,210 +0,0 @@ -from datetime import datetime -import io -import os.path -from textwrap import dedent -import unittest - -from .helpers import StubOpener -from debugger_protocol.schema.upstream import URL as UPSTREAM -from debugger_protocol.schema.metadata import ( - open_metadata, read_metadata, - MetadataError, Metadata) - - -class Stringlike: - - def __init__(self, value): - self.value = value - - def __str__(self): - return self.value - - -class Hash(Stringlike): - pass - - -class OpenMetadataTests(unittest.TestCase): - - def test_success(self): - expected = object() - opener = StubOpener(expected) - schemadir = os.path.join('x', 'y', 'z', '') - metafile, filename = open_metadata(schemadir + 'schema.json', - _open=opener.open) - - self.assertIs(metafile, expected) - self.assertEqual(filename, schemadir + 'UPSTREAM') - - def test_file_missing(self): - metafile = None - opener = StubOpener(metafile) - - with self.assertRaises(MetadataError): - open_metadata('schema.json', _open=opener.open) - - -class ReadMetadataTests(unittest.TestCase): - - def test_success(self): - metafile = io.StringIO(dedent(""" - upstream: https://x.y.z/schema.json - revision: abcdef0123456789 - checksum: deadbeefdeadbeefdeadbeefdeadbeef - downloaded: 2018-01-09 13:10:59 (UTC) - """)) - opener = StubOpener(metafile) - schemadir = os.path.join('x', 'y', 'z', '') - meta, filename = read_metadata(schemadir + 'schema.json', - _open=opener.open) - - self.assertEqual(meta, - Metadata('https://x.y.z/schema.json', - 'abcdef0123456789', - 'deadbeefdeadbeefdeadbeefdeadbeef', - datetime(2018, 1, 9, 13, 10, 59), - )) - self.assertEqual(filename, schemadir + 'UPSTREAM') - - def test_file_missing(self): - metafile = None - opener = StubOpener(metafile) - - with self.assertRaises(MetadataError): - read_metadata('schema.json', _open=opener.open) - - def test_file_invalid(self): - metafile = io.StringIO('') - opener = StubOpener(metafile) - - with self.assertRaises(MetadataError): - read_metadata('schema.json', _open=opener.open) - - -class MetadataTests(unittest.TestCase): - - def test_parse_minimal(self): - expected = Metadata('https://x.y.z/schema.json', - 'abcdef0123456789', - 'deadbeefdeadbeefdeadbeefdeadbeef', - datetime(2018, 1, 9, 13, 10, 59), - ) - meta = Metadata.parse(dedent(""" - upstream: https://x.y.z/schema.json - revision: abcdef0123456789 - checksum: deadbeefdeadbeefdeadbeefdeadbeef - downloaded: 2018-01-09 13:10:59 (UTC) - """)) - - self.assertEqual(meta, expected) - - def test_parse_with_whitespace_and_comments(self): - expected = Metadata('https://x.y.z/schema.json', - 'abcdef0123456789', - 'deadbeefdeadbeefdeadbeefdeadbeef', - datetime(2018, 1, 9, 13, 10, 59), - ) - meta = Metadata.parse(dedent(""" - - # generated by x.y.z - upstream: https://x.y.z/schema.json - - revision: abcdef0123456789 - checksum: deadbeefdeadbeefdeadbeefdeadbeef - downloaded: 2018-01-09 13:10:59 (UTC) - - # done! - - """)) # noqa - - self.assertEqual(meta, expected) - - def test_parse_roundtrip_from_object(self): - orig = Metadata('https://x.y.z/schema.json', - 'abcdef0123456789', - 'deadbeefdeadbeefdeadbeefdeadbeef', - datetime(2018, 1, 9, 13, 10, 59), - ) - meta = Metadata.parse( - orig.format()) - - self.assertEqual(meta, orig) - - def test_parse_roundtrip_from_string(self): - orig = dedent("""\ - upstream: https://x.y.z/schema.json - revision: abcdef0123456789 - checksum: deadbeefdeadbeefdeadbeefdeadbeef - downloaded: 2018-01-09 13:10:59 (UTC) - """) - data = (Metadata.parse(orig) - ).format() - - self.assertEqual(data, orig) - - def test_coercion_noop(self): - meta = Metadata('https://x.y.z/schema.json', - 'abcdef0123456789', - 'deadbeefdeadbeefdeadbeefdeadbeef', - datetime(2018, 1, 9, 13, 10, 59), - ) - - self.assertEqual(meta, ( - 'https://x.y.z/schema.json', - 'abcdef0123456789', - 'deadbeefdeadbeefdeadbeefdeadbeef', - datetime(2018, 1, 9, 13, 10, 59), - )) - - def test_coercion_change_all(self): - meta = Metadata(Stringlike('https://x.y.z/schema.json'), - Hash('abcdef0123456789'), - Hash('deadbeefdeadbeefdeadbeefdeadbeef'), - '2018-01-09 13:10:59 (UTC)', - ) - - self.assertEqual(meta, ( - 'https://x.y.z/schema.json', - 'abcdef0123456789', - 'deadbeefdeadbeefdeadbeefdeadbeef', - datetime(2018, 1, 9, 13, 10, 59), - )) - - def test_validation_fail(self): - baseargs = [ - 'https://x.y.z/schema.json', - 'abcdef0123456789', - 'deadbeefdeadbeefdeadbeefdeadbeef', - datetime(2018, 1, 9, 13, 10, 59), - ] - for i in range(len(baseargs)): - with self.subTest(baseargs[i]): - args = list(baseargs) - args[i] = '' - with self.assertRaises(ValueError): - Metadata(*args) - - def test_url(self): - meta = Metadata(UPSTREAM, - 'abcdef0123456789', - 'deadbeefdeadbeefdeadbeefdeadbeef', - datetime(2018, 1, 9, 13, 10, 59), - ) - url = meta.url - - self.assertEqual(url, 'https://github.com/Microsoft/vscode-debugadapter-node/raw/abcdef0123456789/debugProtocol.json') # noqa - - def test_format(self): - meta = Metadata('https://x.y.z/schema.json', - 'abcdef0123456789', - 'deadbeefdeadbeefdeadbeefdeadbeef', - datetime(2018, 1, 9, 13, 10, 59), - ) - formatted = meta.format() - - self.assertEqual(formatted, dedent("""\ - upstream: https://x.y.z/schema.json - revision: abcdef0123456789 - checksum: deadbeefdeadbeefdeadbeefdeadbeef - downloaded: 2018-01-09 13:10:59 (UTC) - """)) diff --git a/tests/debugger_protocol/schema/test_upstream.py b/tests/debugger_protocol/schema/test_upstream.py deleted file mode 100644 index a251f3d7..00000000 --- a/tests/debugger_protocol/schema/test_upstream.py +++ /dev/null @@ -1,60 +0,0 @@ -from datetime import datetime -import io -import unittest - -from .helpers import StubOpener -from debugger_protocol.schema.file import SchemaFileError -from debugger_protocol.schema.metadata import Metadata -from debugger_protocol.schema.upstream import ( - download, read) - - -class DownloadTests(unittest.TestCase): - - def test_success(self): - now = datetime.utcnow() - infile = io.BytesIO(b'') - outfile = io.BytesIO() - buf = io.BytesIO( - b'[{"sha": "fc2395ca3564fb2afded8d90ddbe38dad1bf86f1"}]') - meta = download('https://github.com/x/y/raw/master/z', - infile, - outfile, - _now=(lambda: now), - _open_url=(lambda _: buf), - ) - rcvd = outfile.getvalue() - - self.assertEqual(meta, Metadata( - 'https://github.com/x/y/raw/master/z', - 'fc2395ca3564fb2afded8d90ddbe38dad1bf86f1', - 'e778c3751f9d0bceaf8d5aa81e2c659f', - now, - )) - self.assertEqual(rcvd, b'') - - -class ReadSchemaTests(unittest.TestCase): - - def test_success(self): - schemafile = io.BytesIO(b'') - buf = io.BytesIO( - b'[{"sha": "fc2395ca3564fb2afded8d90ddbe38dad1bf86f1"}]') - opener = StubOpener(schemafile, buf) - data, meta = read('https://github.com/x/y/raw/master/z', - _open_url=opener.open) - - self.assertEqual(data, b'') - self.assertEqual(meta, Metadata( - 'https://github.com/x/y/raw/master/z', - 'fc2395ca3564fb2afded8d90ddbe38dad1bf86f1', - 'e778c3751f9d0bceaf8d5aa81e2c659f', - meta.downloaded, - )) - - def test_resource_missing(self): - schemafile = None - opener = StubOpener(schemafile) - - with self.assertRaises(SchemaFileError): - read('schema.json', _open_url=opener.open) diff --git a/tests/debugger_protocol/schema/test_util.py b/tests/debugger_protocol/schema/test_util.py deleted file mode 100644 index a4d1658c..00000000 --- a/tests/debugger_protocol/schema/test_util.py +++ /dev/null @@ -1,35 +0,0 @@ -import io -import unittest - -from debugger_protocol.schema._util import get_revision, get_checksum - - -class GetRevisionTests(unittest.TestCase): - - def test_github(self): - buf = io.BytesIO( - b'[{"sha": "fc2395ca3564fb2afded8d90ddbe38dad1bf86f1"}]') - revision = get_revision('https://github.com/x/y/raw/master/z', - _open_url=lambda _: buf) - - self.assertEqual(revision, 'fc2395ca3564fb2afded8d90ddbe38dad1bf86f1') - - def test_unrecognized_url(self): - revision = get_revision('https://localhost/schema.json', - _open_url=lambda _: io.BytesIO()) - - self.assertEqual(revision, '') - - -class GetChecksumTests(unittest.TestCase): - - def test_checksums(self): - checksums = { - b'': 'd41d8cd98f00b204e9800998ecf8427e', - b'spam': 'e09f6a7593f8ae3994ea57e1117f67ec', - } - for data, expected in checksums.items(): - with self.subTest(data): - checksum = get_checksum(data) - - self.assertEqual(checksum, expected) diff --git a/tests/debugger_protocol/schema/test_vendored.py b/tests/debugger_protocol/schema/test_vendored.py deleted file mode 100644 index b47eadf0..00000000 --- a/tests/debugger_protocol/schema/test_vendored.py +++ /dev/null @@ -1,154 +0,0 @@ -import io -from textwrap import dedent -import unittest - -from .helpers import StubOpener -from debugger_protocol.schema.file import SchemaFileError -from debugger_protocol.schema.metadata import MetadataError -from debugger_protocol.schema.vendored import ( - SchemaFileMismatchError, check_local, check_upstream) - - -class CheckLocalTests(unittest.TestCase): - - def test_match(self): - metafile = io.StringIO(dedent(""" - upstream: https://x.y.z/schema.json - revision: abcdef0123456789 - checksum: e778c3751f9d0bceaf8d5aa81e2c659f - downloaded: 2018-01-09 13:10:59 (UTC) - """)) - schemafile = io.BytesIO(b'') - opener = StubOpener(metafile, schemafile) - - # This does not fail. - check_local('schema.json', _open=opener.open) - - def test_mismatch(self): - metafile = io.StringIO(dedent(""" - upstream: https://x.y.z/schema.json - revision: abcdef0123456789 - checksum: abc2 - downloaded: 2018-01-09 13:10:59 (UTC) - """)) - schemafile = io.BytesIO(b'') - opener = StubOpener(metafile, schemafile) - - with self.assertRaises(SchemaFileMismatchError) as cm: - check_local('schema.json', _open=opener.open) - self.assertEqual(str(cm.exception), - ("schema file 'schema.json' does not match " - 'metadata file (checksum mismatch: ' - "'e778c3751f9d0bceaf8d5aa81e2c659f' != 'abc2')")) - - def test_metafile_missing(self): - metafile = None - schemafile = io.BytesIO(b'') - opener = StubOpener(metafile, schemafile) - - with self.assertRaises(MetadataError): - check_local('schema.json', _open=opener.open) - - def test_metafile_invalid(self): - metafile = io.StringIO('') - metafile.name = '/x/y/z/UPSTREAM' - schemafile = io.BytesIO(b'') - opener = StubOpener(metafile, schemafile) - - with self.assertRaises(MetadataError): - check_local('schema.json', _open=opener.open) - - def test_schemafile_missing(self): - metafile = io.StringIO(dedent(""" - upstream: https://x.y.z/schema.json - revision: abcdef0123456789 - checksum: e778c3751f9d0bceaf8d5aa81e2c659f - downloaded: 2018-01-09 13:10:59 (UTC) - """)) - schemafile = None - opener = StubOpener(metafile, schemafile) - - with self.assertRaises(SchemaFileError): - check_local('schema.json', _open=opener.open) - - -class CheckUpstream(unittest.TestCase): - - def test_match(self): - metafile = io.StringIO(dedent(""" - upstream: https://github.com/x/y/raw/master/z - revision: fc2395ca3564fb2afded8d90ddbe38dad1bf86f1 - checksum: e778c3751f9d0bceaf8d5aa81e2c659f - downloaded: 2018-01-09 13:10:59 (UTC) - """)) - schemafile = io.BytesIO(b'') - buf = io.BytesIO( - b'[{"sha": "fc2395ca3564fb2afded8d90ddbe38dad1bf86f1"}]') - opener = StubOpener(metafile, schemafile, buf) - - # This does not fail. - check_upstream('schema.json', - _open=opener.open, _open_url=opener.open) - - def test_revision_mismatch(self): - metafile = io.StringIO(dedent(""" - upstream: https://github.com/x/y/raw/master/z - revision: abc2 - checksum: e778c3751f9d0bceaf8d5aa81e2c659f - downloaded: 2018-01-09 13:10:59 (UTC) - """)) - schemafile = io.BytesIO(b'') - buf = io.BytesIO( - b'[{"sha": "fc2395ca3564fb2afded8d90ddbe38dad1bf86f1"}]') - opener = StubOpener(metafile, schemafile, buf) - - with self.assertRaises(SchemaFileMismatchError) as cm: - check_upstream('schema.json', - _open=opener.open, _open_url=opener.open) - self.assertEqual(str(cm.exception), - ("local schema file 'schema.json' does not match " - "upstream 'https://github.com/x/y/raw/master/z' " - "(revision mismatch: 'abc2' != 'fc2395ca3564fb2afded8d90ddbe38dad1bf86f1')")) # noqa - - def test_checksum_mismatch(self): - metafile = io.StringIO(dedent(""" - upstream: https://github.com/x/y/raw/master/z - revision: fc2395ca3564fb2afded8d90ddbe38dad1bf86f1 - checksum: abc2 - downloaded: 2018-01-09 13:10:59 (UTC) - """)) - schemafile = io.BytesIO(b'') - buf = io.BytesIO( - b'[{"sha": "fc2395ca3564fb2afded8d90ddbe38dad1bf86f1"}]') - opener = StubOpener(metafile, schemafile, buf) - - with self.assertRaises(SchemaFileMismatchError) as cm: - check_upstream('schema.json', - _open=opener.open, _open_url=opener.open) - self.assertEqual(str(cm.exception), - ("local schema file 'schema.json' does not match " - "upstream 'https://github.com/x/y/raw/master/z' " - "(checksum mismatch: 'abc2' != 'e778c3751f9d0bceaf8d5aa81e2c659f')")) # noqa - - def test_metafile_missing(self): - metafile = None - opener = StubOpener(metafile) - - with self.assertRaises(MetadataError): - check_upstream('schema.json', - _open=opener.open, _open_url=opener.open) - - def test_url_resource_missing(self): - metafile = io.StringIO(dedent(""" - upstream: https://github.com/x/y/raw/master/z - revision: fc2395ca3564fb2afded8d90ddbe38dad1bf86f1 - checksum: abc2 - downloaded: 2018-01-09 13:10:59 (UTC) - """)) - #schemafile = io.BytesIO(b'') - schemafile = None - opener = StubOpener(metafile, schemafile) - - with self.assertRaises(SchemaFileError): - check_upstream('schema.json', - _open=opener.open, _open_url=opener.open) diff --git a/pytests/func/__init__.py b/tests/func/__init__.py similarity index 100% rename from pytests/func/__init__.py rename to tests/func/__init__.py diff --git a/pytests/func/test_args.py b/tests/func/test_args.py similarity index 94% rename from pytests/func/test_args.py rename to tests/func/test_args.py index 5dedb1bd..4d019a62 100644 --- a/pytests/func/test_args.py +++ b/tests/func/test_args.py @@ -3,7 +3,7 @@ # for license information. from __future__ import print_function, with_statement, absolute_import -from pytests.helpers.session import DebugSession +from tests.helpers.session import DebugSession import pytest @pytest.mark.parametrize('run_as', ['file', 'module', 'code']) diff --git a/pytests/func/test_attach.py b/tests/func/test_attach.py similarity index 96% rename from pytests/func/test_attach.py rename to tests/func/test_attach.py index b7b247d8..4bebf011 100644 --- a/pytests/func/test_attach.py +++ b/tests/func/test_attach.py @@ -6,9 +6,9 @@ from __future__ import print_function, with_statement, absolute_import import os import pytest -from pytests.helpers.session import DebugSession -from pytests.helpers.pathutils import get_test_root -from pytests.helpers.timeline import Event +from tests.helpers.session import DebugSession +from tests.helpers.pathutils import get_test_root +from tests.helpers.timeline import Event @pytest.mark.parametrize('wait_for_attach', ['waitOn', 'waitOff']) diff --git a/pytests/func/test_break_into_dbg.py b/tests/func/test_break_into_dbg.py similarity index 96% rename from pytests/func/test_break_into_dbg.py rename to tests/func/test_break_into_dbg.py index ce2b481b..7c22027e 100644 --- a/pytests/func/test_break_into_dbg.py +++ b/tests/func/test_break_into_dbg.py @@ -6,8 +6,8 @@ from __future__ import print_function, with_statement, absolute_import import pytest import sys -from pytests.helpers.session import DebugSession -from pytests.helpers.timeline import Event +from tests.helpers.session import DebugSession +from tests.helpers.timeline import Event @pytest.mark.parametrize('run_as', ['file', 'module', 'code']) diff --git a/pytests/func/test_breakpoints.py b/tests/func/test_breakpoints.py similarity index 98% rename from pytests/func/test_breakpoints.py rename to tests/func/test_breakpoints.py index 18ea1ef0..8b7761d6 100644 --- a/pytests/func/test_breakpoints.py +++ b/tests/func/test_breakpoints.py @@ -10,10 +10,10 @@ import pytest import sys import re -from pytests.helpers.pathutils import get_test_root -from pytests.helpers.session import DebugSession -from pytests.helpers.timeline import Event -from pytests.helpers.pattern import ANY, Path +from tests.helpers.pathutils import get_test_root +from tests.helpers.session import DebugSession +from tests.helpers.timeline import Event +from tests.helpers.pattern import ANY, Path BP_TEST_ROOT = get_test_root('bp') diff --git a/pytests/func/test_completions.py b/tests/func/test_completions.py similarity index 97% rename from pytests/func/test_completions.py rename to tests/func/test_completions.py index 92a79535..da0ffa47 100644 --- a/pytests/func/test_completions.py +++ b/tests/func/test_completions.py @@ -5,9 +5,9 @@ from __future__ import print_function, with_statement, absolute_import import pytest -from pytests.helpers.pattern import ANY -from pytests.helpers.session import DebugSession -from pytests.helpers.timeline import Event +from tests.helpers.pattern import ANY +from tests.helpers.session import DebugSession +from tests.helpers.timeline import Event expected_at_line = { diff --git a/pytests/func/test_disconnect.py b/tests/func/test_disconnect.py similarity index 95% rename from pytests/func/test_disconnect.py rename to tests/func/test_disconnect.py index aecbcf99..60b387a4 100644 --- a/pytests/func/test_disconnect.py +++ b/tests/func/test_disconnect.py @@ -6,9 +6,9 @@ from __future__ import print_function, with_statement, absolute_import import os.path import pytest -from pytests.helpers.pattern import ANY -from pytests.helpers.session import DebugSession -from pytests.helpers.timeline import Event +from tests.helpers.pattern import ANY +from tests.helpers.session import DebugSession +from tests.helpers.timeline import Event @pytest.mark.parametrize('start_method', ['attach_socket_cmdline', 'attach_socket_import']) diff --git a/pytests/func/test_django.py b/tests/func/test_django.py similarity index 97% rename from pytests/func/test_django.py rename to tests/func/test_django.py index 7b784b5c..99e56e3e 100644 --- a/pytests/func/test_django.py +++ b/tests/func/test_django.py @@ -8,11 +8,11 @@ import os.path import pytest import sys -from pytests.helpers.pattern import ANY, Path -from pytests.helpers.session import DebugSession -from pytests.helpers.timeline import Event -from pytests.helpers.pathutils import get_test_root -from pytests.helpers.webhelper import get_url_from_str, get_web_content, wait_for_connection +from tests.helpers.pattern import ANY, Path +from tests.helpers.session import DebugSession +from tests.helpers.timeline import Event +from tests.helpers.pathutils import get_test_root +from tests.helpers.webhelper import get_url_from_str, get_web_content, wait_for_connection DJANGO1_ROOT = get_test_root('django1') DJANGO1_MANAGE = os.path.join(DJANGO1_ROOT, 'app.py') diff --git a/pytests/func/test_evaluate.py b/tests/func/test_evaluate.py similarity index 98% rename from pytests/func/test_evaluate.py rename to tests/func/test_evaluate.py index ce70501c..e148c518 100644 --- a/pytests/func/test_evaluate.py +++ b/tests/func/test_evaluate.py @@ -6,9 +6,9 @@ from __future__ import print_function, with_statement, absolute_import import sys -from pytests.helpers.pattern import ANY -from pytests.helpers.session import DebugSession -from pytests.helpers.timeline import Event +from tests.helpers.pattern import ANY +from tests.helpers.session import DebugSession +from tests.helpers.timeline import Event def test_variables_and_evaluate(pyfile, run_as, start_method): diff --git a/pytests/func/test_exception.py b/tests/func/test_exception.py similarity index 97% rename from pytests/func/test_exception.py rename to tests/func/test_exception.py index 7ec5f8e7..f30b0b11 100644 --- a/pytests/func/test_exception.py +++ b/tests/func/test_exception.py @@ -6,9 +6,9 @@ from __future__ import print_function, with_statement, absolute_import import pytest -from pytests.helpers.session import DebugSession -from pytests.helpers.timeline import Event -from pytests.helpers.pattern import ANY, Path +from tests.helpers.session import DebugSession +from tests.helpers.timeline import Event +from tests.helpers.pattern import ANY, Path @pytest.mark.parametrize('raised', ['raisedOn', 'raisedOff']) diff --git a/pytests/func/test_flask.py b/tests/func/test_flask.py similarity index 97% rename from pytests/func/test_flask.py rename to tests/func/test_flask.py index c52d434b..236a1b6d 100644 --- a/pytests/func/test_flask.py +++ b/tests/func/test_flask.py @@ -9,11 +9,11 @@ import platform import pytest import sys -from pytests.helpers.pattern import ANY, Path -from pytests.helpers.session import DebugSession -from pytests.helpers.timeline import Event -from pytests.helpers.webhelper import get_web_content, wait_for_connection -from pytests.helpers.pathutils import get_test_root +from tests.helpers.pattern import ANY, Path +from tests.helpers.session import DebugSession +from tests.helpers.timeline import Event +from tests.helpers.webhelper import get_web_content, wait_for_connection +from tests.helpers.pathutils import get_test_root FLASK1_ROOT = get_test_root('flask1') diff --git a/pytests/func/test_multiproc.py b/tests/func/test_multiproc.py similarity index 98% rename from pytests/func/test_multiproc.py rename to tests/func/test_multiproc.py index bfd827c9..b6d2b331 100644 --- a/pytests/func/test_multiproc.py +++ b/tests/func/test_multiproc.py @@ -8,9 +8,9 @@ import platform import pytest import sys -from pytests.helpers.pattern import ANY -from pytests.helpers.session import DebugSession -from pytests.helpers.timeline import Event, Request, Response +from tests.helpers.pattern import ANY +from tests.helpers.session import DebugSession +from tests.helpers.timeline import Event, Request, Response @pytest.mark.timeout(30) diff --git a/pytests/func/test_output.py b/tests/func/test_output.py similarity index 91% rename from pytests/func/test_output.py rename to tests/func/test_output.py index a3c918f4..3945cc09 100644 --- a/pytests/func/test_output.py +++ b/tests/func/test_output.py @@ -3,9 +3,9 @@ # for license information. from __future__ import print_function, with_statement, absolute_import -from pytests.helpers.session import DebugSession -from pytests.helpers.timeline import Event -from pytests.helpers.pattern import ANY +from tests.helpers.session import DebugSession +from tests.helpers.timeline import Event +from tests.helpers.pattern import ANY def test_with_no_output(pyfile, run_as, start_method): diff --git a/pytests/func/test_path_mapping.py b/tests/func/test_path_mapping.py similarity index 93% rename from pytests/func/test_path_mapping.py rename to tests/func/test_path_mapping.py index d52c2b7d..fb32e4c5 100644 --- a/pytests/func/test_path_mapping.py +++ b/tests/func/test_path_mapping.py @@ -6,9 +6,9 @@ from __future__ import print_function, with_statement, absolute_import import os from shutil import copyfile -from pytests.helpers.pattern import Path -from pytests.helpers.session import DebugSession -from pytests.helpers.timeline import Event +from tests.helpers.pattern import Path +from tests.helpers.session import DebugSession +from tests.helpers.timeline import Event def test_with_path_mappings(pyfile, tmpdir, run_as, start_method): diff --git a/pytests/func/test_run.py b/tests/func/test_run.py similarity index 91% rename from pytests/func/test_run.py rename to tests/func/test_run.py index 8d18adb6..c305e695 100644 --- a/pytests/func/test_run.py +++ b/tests/func/test_run.py @@ -9,10 +9,10 @@ import pytest import ptvsd -from pytests.helpers import print -from pytests.helpers.pattern import ANY -from pytests.helpers.session import DebugSession -from pytests.helpers.timeline import Event +from tests.helpers import print +from tests.helpers.pattern import ANY +from tests.helpers.session import DebugSession +from tests.helpers.timeline import Event @pytest.mark.parametrize('run_as', ['file', 'module', 'code']) diff --git a/pytests/func/test_start_stop.py b/tests/func/test_start_stop.py similarity index 97% rename from pytests/func/test_start_stop.py rename to tests/func/test_start_stop.py index 0fb49b07..b5f9e823 100644 --- a/pytests/func/test_start_stop.py +++ b/tests/func/test_start_stop.py @@ -8,9 +8,9 @@ import platform import pytest import sys -from pytests.helpers.pattern import ANY -from pytests.helpers.session import DebugSession -from pytests.helpers.timeline import Event +from tests.helpers.pattern import ANY +from tests.helpers.session import DebugSession +from tests.helpers.timeline import Event @pytest.mark.parametrize('start_method', ['launch']) diff --git a/pytests/func/test_threads.py b/tests/func/test_threads.py similarity index 94% rename from pytests/func/test_threads.py rename to tests/func/test_threads.py index dbb91ed3..5e0b8355 100644 --- a/pytests/func/test_threads.py +++ b/tests/func/test_threads.py @@ -4,8 +4,8 @@ from __future__ import print_function, with_statement, absolute_import import pytest -from pytests.helpers.timeline import Event -from pytests.helpers.session import DebugSession +from tests.helpers.timeline import Event +from tests.helpers.session import DebugSession @pytest.mark.parametrize('count', [1, 3]) diff --git a/pytests/func/test_vs_specific.py b/tests/func/test_vs_specific.py similarity index 95% rename from pytests/func/test_vs_specific.py rename to tests/func/test_vs_specific.py index 35ea881a..7c10fe35 100644 --- a/pytests/func/test_vs_specific.py +++ b/tests/func/test_vs_specific.py @@ -5,9 +5,9 @@ from __future__ import print_function, with_statement, absolute_import import pytest -from pytests.helpers.session import DebugSession -from pytests.helpers.timeline import Event -from pytests.helpers.pattern import Path +from tests.helpers.session import DebugSession +from tests.helpers.timeline import Event +from tests.helpers.pattern import Path @pytest.mark.parametrize('module', [True, False]) diff --git a/pytests/func/testfiles/attach/attach1.py b/tests/func/testfiles/attach/attach1.py similarity index 100% rename from pytests/func/testfiles/attach/attach1.py rename to tests/func/testfiles/attach/attach1.py diff --git a/pytests/func/testfiles/bp/a&b/test.py b/tests/func/testfiles/bp/a&b/test.py similarity index 100% rename from pytests/func/testfiles/bp/a&b/test.py rename to tests/func/testfiles/bp/a&b/test.py diff --git a/pytests/func/testfiles/bp/ನನ್ನ_ಸ್ಕ್ರಿಪ್ಟ್.py b/tests/func/testfiles/bp/ನನ್ನ_ಸ್ಕ್ರಿಪ್ಟ್.py similarity index 100% rename from pytests/func/testfiles/bp/ನನ್ನ_ಸ್ಕ್ರಿಪ್ಟ್.py rename to tests/func/testfiles/bp/ನನ್ನ_ಸ್ಕ್ರಿಪ್ಟ್.py diff --git a/debugger_protocol/__init__.py b/tests/func/testfiles/django1/__init__.py similarity index 100% rename from debugger_protocol/__init__.py rename to tests/func/testfiles/django1/__init__.py diff --git a/pytests/func/testfiles/django1/app.py b/tests/func/testfiles/django1/app.py similarity index 100% rename from pytests/func/testfiles/django1/app.py rename to tests/func/testfiles/django1/app.py diff --git a/pytests/func/testfiles/django1/templates/hello.html b/tests/func/testfiles/django1/templates/hello.html similarity index 100% rename from pytests/func/testfiles/django1/templates/hello.html rename to tests/func/testfiles/django1/templates/hello.html diff --git a/pytests/func/testfiles/django1/__init__.py b/tests/func/testfiles/flask1/__init__.py similarity index 100% rename from pytests/func/testfiles/django1/__init__.py rename to tests/func/testfiles/flask1/__init__.py diff --git a/pytests/func/testfiles/flask1/app.py b/tests/func/testfiles/flask1/app.py similarity index 100% rename from pytests/func/testfiles/flask1/app.py rename to tests/func/testfiles/flask1/app.py diff --git a/pytests/func/testfiles/flask1/templates/hello.html b/tests/func/testfiles/flask1/templates/hello.html similarity index 100% rename from pytests/func/testfiles/flask1/templates/hello.html rename to tests/func/testfiles/flask1/templates/hello.html diff --git a/pytests/func/testfiles/flask1/__init__.py b/tests/func/testfiles/testpkgs/pkg1/__init__.py similarity index 100% rename from pytests/func/testfiles/flask1/__init__.py rename to tests/func/testfiles/testpkgs/pkg1/__init__.py diff --git a/pytests/func/testfiles/testpkgs/pkg1/__main__.py b/tests/func/testfiles/testpkgs/pkg1/__main__.py similarity index 100% rename from pytests/func/testfiles/testpkgs/pkg1/__main__.py rename to tests/func/testfiles/testpkgs/pkg1/__main__.py diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py index 530435f8..2b15ffd1 100644 --- a/tests/helpers/__init__.py +++ b/tests/helpers/__init__.py @@ -1,3 +1,59 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See LICENSE in the project root +# for license information. -def noop(*args, **kwargs): - """Do nothing.""" +from __future__ import print_function, with_statement, absolute_import + +import os +import sys +import threading +import time +import traceback + + +if sys.version_info >= (3, 5): + clock = time.monotonic +else: + clock = time.clock + + +timestamp_zero = clock() + +def timestamp(): + return clock() - timestamp_zero + + +def dump_stacks(): + """Dump the stacks of all threads except the current thread""" + current_ident = threading.current_thread().ident + for thread_ident, frame in sys._current_frames().items(): + if thread_ident == current_ident: + continue + for t in threading.enumerate(): + if t.ident == thread_ident: + thread_name = t.name + thread_daemon = t.daemon + break + else: + thread_name = '' + print('Stack of %s (%s) in pid %s; daemon=%s' % (thread_name, thread_ident, os.getpid(), thread_daemon)) + print(''.join(traceback.format_stack(frame))) + + +def dump_stacks_in(secs): + """Invokes dump_stacks() on a background thread after waiting. + + Can be called from debugged code before the point after which it hangs, + to determine the cause of the hang while debugging a test. + """ + + def dumper(): + time.sleep(secs) + dump_stacks() + + thread = threading.Thread(target=dumper) + thread.daemon = True + thread.start() + + +from .printer import print diff --git a/tests/helpers/_io.py b/tests/helpers/_io.py deleted file mode 100644 index 3000bd42..00000000 --- a/tests/helpers/_io.py +++ /dev/null @@ -1,146 +0,0 @@ -import contextlib -from io import StringIO, BytesIO -import sys - -from . import noop - - -if sys.version_info < (3,): - Buffer = BytesIO -else: - Buffer = StringIO - - -@contextlib.contextmanager -def captured_stdio(out=None, err=None): - if out is None and err is None: - out = err = Buffer() - else: - if out is True: - out = Buffer() - elif out is False: - out = None - if err is True: - err = Buffer() - elif err is False: - err = None - - orig = sys.stdout, sys.stderr - if out is not None: - sys.stdout = out - if err is not None: - sys.stderr = err - try: - yield out, err - finally: - sys.stdout, sys.stderr = orig - - -def iter_lines(read, sep=b'\n', stop=noop): - """Yield each sep-delimited line. - - If EOF is hit, the loop is stopped, or read() returns b'' then - EOFError is raised with exc.remainder set to any bytes left in the - buffer. - """ - first = sep[0] - line = b'' - while True: - try: - if stop(): - raise EOFError() - c = read(1) - if not c: - raise EOFError() - except EOFError as exc: - exc.buffered = line - raise - line += c - if c != first: - continue - - for want in sep[1:]: - try: - if stop(): - raise EOFError() - c = read(1) - if not c: - raise EOFError() - except EOFError as exc: - exc.buffered = line - raise - line += c - if c != want: - break - else: - # EOL - yield line - line = b'' - - -def iter_lines_buffered(read, sep=b'\n', initial=b'', stop=noop): - """Yield (line, remainder) for each sep-delimited line. - - If EOF is hit, the loop is stopped, or read() returns b'' then - EOFError is raised with exc.remainder set to any bytes left in the - buffer. - """ - gap = len(sep) - # TODO: Use a bytearray? - buf = b'' - data = initial - while True: - try: - line = data[:data.index(sep)] - except ValueError: - buf += data - try: - if stop(): - raise EOFError() - # ConnectionResetError (errno 104) likely means the - # client was never able to establish a connection. - # TODO: Handle ConnectionResetError gracefully. - data = read(1024) - if not data: - raise EOFError() - if buf and buf[-1:] == b'\r': - data = buf + data - buf = b'' - except EOFError as exc: - exc.remainder = buf - raise - else: - # EOL - data = data[len(line) + gap:] - yield buf + line, data - buf = b'' - - -def read_buffered(read, numbytes, initial=b'', stop=noop): - """Return (data, remainder) with read(). - - If EOF is hit, the loop is stopped, or read() returns b'' then - EOFError is raised with exc.buffered set to any bytes left in the - buffer. - """ - # TODO: Use a bytearray? - buf = initial - while len(buf) < numbytes: - try: - if stop(): - raise EOFError() - data = read(1024) - if not data: - raise EOFError() - except EOFError as exc: - exc.buffered = buf - raise - buf += data - return buf[:numbytes], buf[numbytes:] - - -def write_all(write, data, stop=noop): - """Keep writing until all the data is written.""" - while data and not stop(): - sent = write(data) - data = data[sent:] diff --git a/tests/helpers/argshelper.py b/tests/helpers/argshelper.py deleted file mode 100644 index 778e3860..00000000 --- a/tests/helpers/argshelper.py +++ /dev/null @@ -1,11 +0,0 @@ -PROG = 'eggs' -PORT_ARGS = ['--port', '8888'] -PYDEVD_DEFAULT_ARGS = ['--qt-support=auto'] - - -def _get_args(*args, **kwargs): - ptvsd_extras = kwargs.get('ptvsd_extras', []) - prog = [kwargs.get('prog', PROG)] - port = kwargs.get('port', PORT_ARGS) - pydevd_args = kwargs.get('pydevd', PYDEVD_DEFAULT_ARGS) - return prog + port + ptvsd_extras + pydevd_args + list(args) diff --git a/pytests/helpers/colors.py b/tests/helpers/colors.py similarity index 100% rename from pytests/helpers/colors.py rename to tests/helpers/colors.py diff --git a/tests/helpers/counter.py b/tests/helpers/counter.py deleted file mode 100644 index 343b3d96..00000000 --- a/tests/helpers/counter.py +++ /dev/null @@ -1,64 +0,0 @@ -import sys - - -class Counter(object): - """An introspectable, dynamic alternative to itertools.count().""" - - def __init__(self, start=0, step=1): - self._start = int(start) - self._step = int(step) - - def __repr__(self): - return '{}(start={}, step={})'.format( - type(self).__name__, - self.peek(), - self._step, - ) - - def __iter__(self): - return self - - def __next__(self): - try: - self._last += self._step - except AttributeError: - self._last = self._start - return self._last - - if sys.version_info[0] == 2: - next = __next__ - - @property - def start(self): - return self._start - - @property - def step(self): - return self._step - - @property - def last(self): - try: - return self._last - except AttributeError: - return None - - def peek(self, iterations=1): - """Return the value that will be used next.""" - try: - last = self._last - except AttributeError: - last = self._start - self._step - return last + self._step * iterations - - def reset(self, start=None): - """Set the next value to the given one. - - If no value is provided then the previous start value is used. - """ - if start is not None: - self._start = int(start) - try: - del self._last - except AttributeError: - pass diff --git a/tests/helpers/debugadapter.py b/tests/helpers/debugadapter.py deleted file mode 100644 index ab7a73f2..00000000 --- a/tests/helpers/debugadapter.py +++ /dev/null @@ -1,288 +0,0 @@ -import os -import os.path -import socket -import time - -from ptvsd.socket import Address -from ptvsd._util import Closeable, ClosedError -from .proc import Proc -from .. import PROJECT_ROOT - - -COPIED_ENV = [ - 'PYTHONHASHSEED', - - # Windows - #'ALLUSERSPROFILE', - #'APPDATA', - #'CLIENTNAME', - #'COMMONPROGRAMFILES', - #'COMMONPROGRAMFILES(X86)', - #'COMMONPROGRAMW6432', - #'COMPUTERNAME', - #'COMSPEC', - #'DRIVERDATA', - #'HOMEDRIVE', - #'HOMEPATH', - #'LOCALAPPDATA', - #'LOGONSERVER', - #'NUMBER_OF_PROCESSORS', - #'OS', - #'PATH', - #'PATHEXT', - #'PROCESSOR_ARCHITECTURE', - #'PROCESSOR_IDENTIFIER', - #'PROCESSOR_LEVEL', - #'PROCESSOR_REVISION', - #'PROGRAMDATA', - #'PROGRAMFILES', - #'PROGRAMFILES(X86)', - #'PROGRAMW6432', - #'PSMODULEPATH', - #'PUBLIC', - #'SESSIONNAME', - 'SYSTEMDRIVE', - 'SYSTEMROOT', - #'TEMP', - #'TMP', - #'USERDOMAIN', - #'USERDOMAIN_ROAMINGPROFILE', - #'USERNAME', - #'USERPROFILE', - 'WINDIR', -] - -SERVER_READY_TIMEOUT = 3.0 # seconds - -try: - ConnectionRefusedError -except Exception: - class ConnectionRefusedError(Exception): - pass - - -def _copy_env(verbose=False, env=None): - variables = {k: v for k, v in os.environ.items() if k in COPIED_ENV} - # TODO: Be smarter about the seed? - variables.setdefault('PYTHONHASHSEED', '1234') - if verbose: - variables.update({ - 'PTVSD_DEBUG': '1', - 'PTVSD_SOCKET_TIMEOUT': '1', - }) - if env is not None: - variables.update(env) - - # Ensure Project root is always in current path. - python_path = variables.get('PYTHONPATH', None) - if python_path is None: - variables['PYTHONPATH'] = PROJECT_ROOT - else: - variables['PYTHONPATH'] = os.pathsep.join([PROJECT_ROOT, python_path]) - - return variables - - -def wait_for_socket_server(addr, timeout=SERVER_READY_TIMEOUT): - start_time = time.time() - while True: - try: - sock = socket.create_connection((addr.host, addr.port)) - sock.close() - time.sleep(0.1) # wait for daemon to detect to socket close. - return - except Exception: - pass - time.sleep(0.1) - if time.time() - start_time > timeout: - raise ConnectionRefusedError('Timeout waiting for connection') - - -def wait_for_port_to_free(port, timeout=3.0): - start_time = time.time() - while True: - try: - time.sleep(0.5) - sock = socket.create_connection(('localhost', port)) - sock.close() - except Exception: - return - time.sleep(0.1) - if time.time() - start_time > timeout: - raise ConnectionRefusedError('Timeout waiting for port to be free') - - -class DebugAdapter(Closeable): - - VERBOSE = False - #VERBOSE = True - - PORT = 8888 - - # generic factories - - @classmethod - def start(cls, argv, env=None, cwd=None, **kwargs): - def new_proc(argv, addr, **kwds): - env_vars = _copy_env(verbose=cls.VERBOSE, env=env) - argv = list(argv) - cls._ensure_addr(argv, addr) - return Proc.start_python_module( - 'ptvsd', argv, env=env_vars, cwd=cwd, **kwds) - - return cls._start(new_proc, argv, **kwargs) - - @classmethod - def start_wrapper_script(cls, filename, argv, env=None, cwd=None, - **kwargs): # noqa - def new_proc(argv, addr, **kwds): - env_vars = _copy_env(verbose=cls.VERBOSE, env=env) - return Proc.start_python_script( - filename, argv, env=env_vars, cwd=cwd, **kwds) - - return cls._start(new_proc, argv, **kwargs) - - @classmethod - def start_wrapper_module(cls, - modulename, - argv, - env=None, - cwd=None, - **kwargs): # noqa - def new_proc(argv, addr, **kwds): - env_vars = _copy_env(verbose=cls.VERBOSE, env=env) - return Proc.start_python_module( - modulename, argv, env=env_vars, cwd=cwd, **kwds) - - return cls._start(new_proc, argv, **kwargs) - - # specific factory cases - - @classmethod - def start_nodebug(cls, addr, name, kind='script', **kwargs): - if kind == 'script': - argv = ['--nodebug', name] - elif kind == 'module': - argv = ['--nodebug', '-m', name] - else: - raise NotImplementedError - return cls.start(argv, addr=addr, **kwargs) - - @classmethod - def start_as_server(cls, addr, *args, **kwargs): - addr = Address.as_server(*addr) - return cls._start_as(addr, *args, server=False, **kwargs) - - @classmethod - def start_as_client(cls, addr, *args, **kwargs): - addr = Address.as_client(*addr) - return cls._start_as(addr, *args, server=False, **kwargs) - - @classmethod - def start_for_attach(cls, addr, *args, **kwargs): - srvtimeout = kwargs.pop('srvtimeout', SERVER_READY_TIMEOUT) - addr = Address.as_server(*addr) - adapter = cls._start_as(addr, *args, server=True, **kwargs) - if srvtimeout is not None: - wait_for_socket_server(addr, timeout=srvtimeout) - return adapter - - @classmethod - def _start_as(cls, - addr, - name, - kind='script', - extra=None, - server=False, - **kwargs): - argv = [] - if server: - argv += ['--server'] - if kwargs.pop('wait', True): - argv += ['--wait'] - if kind == 'script': - argv += [name] - elif kind == 'module': - argv += ['-m', name] - else: - raise NotImplementedError - if extra: - argv += list(extra) - return cls.start(argv, addr=addr, **kwargs) - - @classmethod - def start_embedded(cls, addr, filename, argv=[], **kwargs): - # ptvsd.enable_attach() slows things down, so we must wait longer. - srvtimeout = kwargs.pop('srvtimeout', SERVER_READY_TIMEOUT + 2) - addr = Address.as_server(*addr) - with open(filename, 'r+') as scriptfile: - content = scriptfile.read() - # TODO: Handle this case somehow? - assert 'ptvsd.enable_attach' in content - adapter = cls.start_wrapper_script( - filename, argv=argv, addr=addr, **kwargs) - if srvtimeout is not None: - wait_for_socket_server(addr, timeout=srvtimeout) - return adapter - - @classmethod - def _start(cls, new_proc, argv, addr=None, **kwargs): - addr = Address.from_raw(addr, defaultport=cls.PORT) - proc = new_proc(argv, addr, **kwargs) - return cls(proc, addr, owned=True) - - @classmethod - def _ensure_addr(cls, argv, addr): - if '--host' in argv: - raise ValueError("unexpected '--host' in argv") - if '--port' in argv: - raise ValueError("unexpected '--port' in argv") - if '--client' in argv: - raise ValueError("unexpected '--client' in argv") - host, port = addr - - argv.insert(0, str(port)) - argv.insert(0, '--port') - - argv.insert(0, host) - argv.insert(0, '--host') - if not addr.isserver: - argv.insert(0, '--client') - - def __init__(self, proc, addr, owned=False): - super(DebugAdapter, self).__init__() - assert isinstance(proc, Proc) - self._proc = proc - self._addr = addr - - @property - def address(self): - return self._addr - - @property - def pid(self): - return self._proc.pid - - @property - def output(self): - # TODO: Decode here? - return self._proc.output - - @property - def exitcode(self): - return self._proc.exitcode - - def wait(self, *argv): - self._proc.wait(*argv) - - # internal methods - - def _close(self): - if self._proc is not None: - try: - self._proc.close() - except ClosedError: - pass - if self.VERBOSE: - lines = self.output.decode('utf-8').splitlines() - print(' + ' + '\n + '.join(lines)) diff --git a/tests/helpers/debugclient.py b/tests/helpers/debugclient.py deleted file mode 100644 index 3149a27a..00000000 --- a/tests/helpers/debugclient.py +++ /dev/null @@ -1,270 +0,0 @@ -from __future__ import absolute_import - -import os -import traceback -import warnings - -from ptvsd.socket import Address -from ptvsd._util import new_hidden_thread, Closeable, ClosedError -from .debugadapter import DebugAdapter, wait_for_socket_server -from .debugsession import DebugSession - -# TODO: Add a helper function to start a remote debugger for testing -# remote debugging? - - -class _LifecycleClient(Closeable): - - SESSION = DebugSession - - def __init__( - self, - addr=None, - port=8888, - breakpoints=None, - connecttimeout=1.0, - ): - super(_LifecycleClient, self).__init__() - self._addr = Address.from_raw(addr, defaultport=port) - self._connecttimeout = connecttimeout - self._adapter = None - self._session = None - - self._breakpoints = breakpoints - - @property - def adapter(self): - return self._adapter - - @property - def session(self): - return self._session - - def start_debugging(self, launchcfg): - if self.closed: - raise RuntimeError('debug client closed') - if self._adapter is not None: - raise RuntimeError('debugger already running') - assert self._session is None - - raise NotImplementedError - - def stop_debugging(self): - if self.closed: - raise RuntimeError('debug client closed') - if self._adapter is None: - raise RuntimeError('debugger not running') - - if self._session is not None: - self._detach() - - try: - self._adapter.close() - except ClosedError: - pass - self._adapter = None - - def attach_pid(self, pid, **kwargs): - if self.closed: - raise RuntimeError('debug client closed') - if self._adapter is None: - raise RuntimeError('debugger not running') - if self._session is not None: - raise RuntimeError('already attached') - - raise NotImplementedError - - def attach_socket(self, addr=None, adapter=None, **kwargs): - if self.closed: - raise RuntimeError('debug client closed') - if adapter is None: - adapter = self._adapter - elif self._adapter is not None: - raise RuntimeError('already using managed adapter') - if adapter is None: - raise RuntimeError('debugger not running') - if self._session is not None: - raise RuntimeError('already attached') - - if addr is None: - addr = adapter.address - self._attach(addr, **kwargs) - return self._session - - def detach(self, adapter=None): - if self.closed: - raise RuntimeError('debug client closed') - if self._session is None: - raise RuntimeError('not attached') - if adapter is None: - adapter = self._adapter - assert adapter is not None - if not self._session.is_client: - raise RuntimeError('detach not supported') - - self._detach() - - # internal methods - - def _close(self): - if self._session is not None: - try: - self._session.close() - except ClosedError: - pass - if self._adapter is not None: - try: - self._adapter.close() - except ClosedError: - pass - - def _launch(self, - argv, - script=None, - wait_for_connect=None, - detachable=True, - env=None, - cwd=None, - **kwargs): - if script is not None: - def start(*args, **kwargs): - return DebugAdapter.start_wrapper_script( - script, *args, **kwargs) - else: - start = DebugAdapter.start - new_addr = Address.as_server if detachable else Address.as_client - addr = new_addr(None, self._addr.port) - self._adapter = start(argv, addr=addr, env=env, cwd=cwd) - - if wait_for_connect: - wait_for_connect() - else: - try: - wait_for_socket_server(addr) - except Exception: - # If we fail to connect, print out the adapter output. - self._adapter.VERBOSE = True - raise - self._attach(addr, **kwargs) - - def _attach(self, addr, **kwargs): - if addr is None: - addr = self._addr - assert addr.host == 'localhost' - self._session = self.SESSION.create_client(addr, **kwargs) - - def _detach(self): - session = self._session - if session is None: - return - self._session = None - try: - session.close() - except ClosedError: - pass - - -class DebugClient(_LifecycleClient): - """A high-level abstraction of a debug client (i.e. editor).""" - - # TODO: Manage breakpoints, etc. - # TODO: Add debugger methods here (e.g. "pause"). - - -class EasyDebugClient(DebugClient): - def start_detached(self, argv): - """Start an adapter in a background process.""" - if self.closed: - raise RuntimeError('debug client closed') - if self._adapter is not None: - raise RuntimeError('debugger already running') - assert self._session is None - - # TODO: Launch, handshake and detach? - self._adapter = DebugAdapter.start(argv, port=self._port) - return self._adapter - - def host_local_debugger(self, - argv, - script=None, - env=None, - cwd=None, - **kwargs): # noqa - if self.closed: - raise RuntimeError('debug client closed') - if self._adapter is not None: - raise RuntimeError('debugger already running') - assert self._session is None - addr = ('localhost', self._addr.port) - - self._run_server_ex = None - - def run(): - try: - self._session = self.SESSION.create_server(addr, **kwargs) - except Exception: - self._run_server_ex = traceback.format_exc() - - t = new_hidden_thread( - target=run, - name='test.client', - ) - t.start() - - def wait(): - t.join(timeout=self._connecttimeout) - if t.is_alive(): - warnings.warn('timed out waiting for connection') - if self._session is None: - message = 'unable to connect after {} secs'.format( # noqa - self._connecttimeout) - if self._run_server_ex is None: - raise Exception(message) - else: - message = message + os.linesep + self._run_server_ex # noqa - raise Exception(message) - - # The adapter will close when the connection does. - - self._launch( - argv, - script=script, - wait_for_connect=wait, - detachable=False, - env=env, - cwd=cwd) - - return self._adapter, self._session - - def launch_script(self, filename, *argv, **kwargs): - if self.closed: - raise RuntimeError('debug client closed') - if self._adapter is not None: - raise RuntimeError('debugger already running') - assert self._session is None - - argv = [ - filename, - ] + list(argv) - if kwargs.pop('nodebug', False): - argv.insert(0, '--nodebug') - if kwargs.pop('wait', True): - argv.insert(0, '--wait') - self._launch(argv, **kwargs) - return self._adapter, self._session - - def launch_module(self, module, *argv, **kwargs): - if self.closed: - raise RuntimeError('debug client closed') - if self._adapter is not None: - raise RuntimeError('debugger already running') - assert self._session is None - - argv = [ - '-m', - module, - ] + list(argv) - if kwargs.pop('nodebug', False): - argv.insert(0, '--nodebug') - self._launch(argv, **kwargs) - return self._adapter, self._session diff --git a/pytests/helpers/debuggee/__init__.py b/tests/helpers/debuggee/__init__.py similarity index 90% rename from pytests/helpers/debuggee/__init__.py rename to tests/helpers/debuggee/__init__.py index 55957186..ca4dcfa9 100644 --- a/pytests/helpers/debuggee/__init__.py +++ b/tests/helpers/debuggee/__init__.py @@ -8,7 +8,7 @@ from __future__ import print_function, with_statement, absolute_import # the code that is executed under debugger as part of the test (e.g. via @pyfile). # PYTHONPATH has an entry appended to it that allows these modules to be imported # directly from such code, i.e. "import backchannel". Consequently, these modules -# should not assume that any other code from pytests/ is importable. +# should not assume that any other code from tests/ is importable. # Ensure that __file__ is always absolute. diff --git a/pytests/helpers/debuggee/backchannel.py b/tests/helpers/debuggee/backchannel.py similarity index 100% rename from pytests/helpers/debuggee/backchannel.py rename to tests/helpers/debuggee/backchannel.py diff --git a/pytests/helpers/debuggee/dbgimporter.py b/tests/helpers/debuggee/dbgimporter.py similarity index 100% rename from pytests/helpers/debuggee/dbgimporter.py rename to tests/helpers/debuggee/dbgimporter.py diff --git a/tests/helpers/debugsession.py b/tests/helpers/debugsession.py deleted file mode 100644 index 51dc37e4..00000000 --- a/tests/helpers/debugsession.py +++ /dev/null @@ -1,411 +0,0 @@ -from __future__ import absolute_import, print_function - -import contextlib -import json -import socket -import sys -import time -import threading -import warnings - -from ptvsd._util import new_hidden_thread, Closeable, ClosedError -from .message import ( - raw_read_all as read_messages, - raw_write_one as write_message -) -from .socket import ( - Connection, create_server, create_client, close, - recv_as_read, send_as_write, - timeout as socket_timeout) -from .threading import get_locked_and_waiter -from .vsc import parse_message - - -class DebugSessionConnection(Closeable): - - VERBOSE = False - #VERBOSE = True - - TIMEOUT = 5.0 - - @classmethod - def create_client(cls, addr, **kwargs): - def connect(addr, timeout): - sock = create_client() - for _ in range(int(timeout * 10)): - try: - sock.connect(addr) - except (OSError, socket.error): - if cls.VERBOSE: - print('+', end='') - sys.stdout.flush() - time.sleep(0.1) - else: - break - else: - raise RuntimeError('could not connect') - return sock - return cls._create(connect, addr, **kwargs) - - @classmethod - def create_server(cls, addr, **kwargs): - def connect(addr, timeout): - server = create_server(addr) - with socket_timeout(server, timeout): - client, _ = server.accept() - return Connection(client, server) - return cls._create(connect, addr, **kwargs) - - @classmethod - def _create(cls, connect, addr, timeout=None): - if timeout is None: - timeout = cls.TIMEOUT - sock = connect(addr, timeout) - 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 - - @property - def is_client(self): - try: - return self._sock.server is None - except AttributeError: - return True - - def iter_messages(self): - if self.closed: - raise RuntimeError('connection closed') - - def stop(): - return self.closed - read = recv_as_read(self._sock) - for msg, _, _ in read_messages(read, stop=stop): - if self.VERBOSE: - print(repr(msg)) - yield parse_message(msg) - - def send(self, req): - if self.closed: - raise RuntimeError('connection closed') - - 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 - - HOST = 'localhost' - PORT = 8888 - - TIMEOUT = None - - @classmethod - def create_client(cls, addr=None, **kwargs): - if addr is None: - addr = (cls.HOST, cls.PORT) - conn = DebugSessionConnection.create_client( - addr, - timeout=kwargs.get('timeout'), - ) - return cls(conn, owned=True, **kwargs) - - @classmethod - def create_server(cls, addr=None, **kwargs): - if addr is None: - addr = (cls.HOST, cls.PORT) - conn = DebugSessionConnection.create_server(addr, **kwargs) - 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 = new_hidden_thread( - target=self._listen, - name='test.session', - ) - self._listenerthread.start() - - @property - def is_client(self): - return self._conn.is_client - - @property - def received(self): - return list(self._received) - - def _create_request(self, command, **args): - seq = self._seq - self._seq += 1 - return { - 'type': 'request', - 'seq': seq, - 'command': command, - 'arguments': args, - } - - def send_request(self, command, **args): - if self.closed: - raise RuntimeError('session closed') - - wait = args.pop('wait', False) - req = self._create_request(command, **args) - if self.VERBOSE: - msg = parse_message(req) - print(' <-', msg) - - if wait: - with self.wait_for_response(req) as resp: - self._conn.send(req) - resp_awaiter = AwaitableResponse(req, lambda: resp["msg"]) - else: - resp_awaiter = self._get_awaiter_for_request(req, **args) - self._conn.send(req) - return resp_awaiter - - def add_handler(self, handler, **kwargs): - if self.closed: - raise RuntimeError('session closed') - - self._add_handler(handler, **kwargs) - - @contextlib.contextmanager - def wait_for_event(self, event, **kwargs): - if self.closed: - raise RuntimeError('session closed') - result = {'msg': None} - - def match(msg): - result['msg'] = msg - return msg.type == 'event' and msg.event == event - handlername = 'event {!r}'.format(event) - with self._wait_for_message(match, handlername, **kwargs): - yield result - - def get_awaiter_for_event(self, event, condition=lambda msg: True, **kwargs): # noqa - if self.closed: - raise RuntimeError('session closed') - result = {'msg': None} - - def match(msg): - result['msg'] = msg - return msg.type == 'event' and msg.event == event and condition(msg) # noqa - handlername = 'event {!r}'.format(event) - evt = self._get_message_handle(match, handlername) - - return AwaitableEvent(event, lambda: result["msg"], evt) - - def _get_awaiter_for_request(self, req, **kwargs): - if self.closed: - raise RuntimeError('session closed') - - try: - command, seq = req.command, req.seq - except AttributeError: - command, seq = req['command'], req['seq'] - result = {'msg': None} - - def match(msg): - if msg.type != 'response': - return False - result['msg'] = msg - return msg.request_seq == seq - handlername = 'response (cmd:{} seq:{})'.format(command, seq) - evt = self._get_message_handle(match, handlername) - - return AwaitableResponse(req, lambda: result["msg"], evt) - - @contextlib.contextmanager - def wait_for_response(self, req, **kwargs): - if self.closed: - raise RuntimeError('session closed') - - try: - command, seq = req.command, req.seq - except AttributeError: - command, seq = req['command'], req['seq'] - result = {'msg': None} - - def match(msg): - if msg.type != 'response': - return False - result['msg'] = msg - return msg.request_seq == seq - handlername = 'response (cmd:{} seq:{})'.format(command, seq) - with self._wait_for_message(match, handlername, **kwargs): - yield result - - # internal methods - - def _close(self): - if self._owned: - try: - self._conn.close() - except ClosedError: - pass - 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): - eof = None - try: - for msg in self._conn.iter_messages(): - if self.VERBOSE: - print(' ->', msg) - self._receive_message(msg) - except EOFError as ex: - # Handle EOF outside of except to avoid unnecessary chaining. - eof = ex - if eof: - remainder = getattr(eof, 'remainder', b'') - if remainder: - self._receive_message(remainder) - try: - self.close() - except ClosedError: - pass - - 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): - if timeout is None: - timeout = self.TIMEOUT - 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, fail=True) - - def _get_message_handle(self, match, handlername): - event = threading.Event() - - def handler(msg): - if not match(msg): - return msg, False - event.set() - return msg, True - self._add_handler(handler, handlername, False) - return event - - -class Awaitable(object): - - @classmethod - def wait_all(cls, *awaitables): - timeout = 3.0 - messages = [] - for _ in range(int(timeout * 10)): - time.sleep(0.1) - messages = [] - not_ready = (a for a in awaitables if a._event is not None and not a._event.is_set()) # noqa - for awaitable in not_ready: - if isinstance(awaitable, AwaitableEvent): - messages.append('Event {}'.format(awaitable.name)) - else: - messages.append('Response {}'.format(awaitable.name)) - if len(messages) == 0: - return - else: - raise TimeoutError('Timeout waiting for {}'.format(','.join(messages))) # noqa - - def __init__(self, name, event=None): - self._event = event - self.name = name - - def wait(self, timeout=1.0): - if self._event is None: - return - if not self._event.wait(timeout): - message = 'Timeout waiting for ' - if isinstance(self, AwaitableEvent): - message += 'Event {}'.format(self.name) - else: - message += 'Response {}'.format(self.name) - raise TimeoutError(message) - - -class AwaitableResponse(Awaitable): - - def __init__(self, req, result_getter, event=None): - super(AwaitableResponse, self).__init__(req["command"], event) - self.req = req - self._result_getter = result_getter - - @property - def resp(self): - return self._result_getter() - - -class AwaitableEvent(Awaitable): - - def __init__(self, name, result_getter, event=None): - super(AwaitableEvent, self).__init__(name, event) - self._result_getter = result_getter - - @property - def event(self): - return self._result_getter() diff --git a/tests/helpers/header.py b/tests/helpers/header.py deleted file mode 100644 index e5c83c7d..00000000 --- a/tests/helpers/header.py +++ /dev/null @@ -1,78 +0,0 @@ -from . import noop -from ._io import iter_lines_buffered, write_all - - -class HeaderError(Exception): - """Some header-related problem.""" - - -class HeaderLineError(HeaderError): - """A problem with an encoded header line.""" - - -class DecodeError(HeaderLineError): - """Trouble decoding a header line.""" - - -def decode(line): - """Return (name, value) for the given encoded header line.""" - if line[-2:] == b'\r\n': - line = line[:-2] - if not line: - return None, None - line = line.decode('ascii', 'replace') - name, sep, value = line.partition(':') - if not sep: - raise DecodeError(line) - return name, value - - -def encode(name, value): - """Return the encoded header line.""" - return '{}: {}\r\n'.format(name, value).encode('ascii') - - -def read_one(read, **kwargs): - """Return ((name, value), remainder) for the next header from read().""" - lines = iter_lines_buffered(read, sep=b'\r\n', **kwargs) - for line, remainder in lines: - if not line: - return None, remainder - return decode(line), remainder - - -def write_one(write, name, value, stop=noop): - """Send the header.""" - line = encode(name, value) - return write_all(write, line, stop=stop) - - -#def recv_header(sock, stop=(lambda: None), timeout=5.0): -# """Return (name, value) for the next header.""" -# line = b'' -# with socket.timeout(sock, timeout): -# while not stop(): -# c = sock.recv(1) -# if c == b'\r': -# c = sock.recv(1) -# if c == b'\n': -# break -# line += b'\r' -# line += c -# else: -# line += c -# line = line.decode('ascii', 'replace') -# if not line: -# return None, None -# name, sep, value = line.partition(':') -# if not sep: -# raise ValueError('bad header line {!r}'.format(line)) -# return name, value -# -# -#def send_header(sock, name, value): -# """Send the header.""" -# line = '{}: {}\r\n'.format(name, value).encode('ascii') -# while line: -# sent = sock.send(line) -# line = line[sent:] diff --git a/tests/helpers/http.py b/tests/helpers/http.py deleted file mode 100644 index 296fdab5..00000000 --- a/tests/helpers/http.py +++ /dev/null @@ -1,82 +0,0 @@ -from __future__ import absolute_import - -try: - from http.server import BaseHTTPRequestHandler, HTTPServer -except ImportError: - from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer - -from ptvsd._util import new_hidden_thread - - -class Server: - """Wraps an http.server.HTTPServer in a thread.""" - - def __init__(self, handler, host='', port=8000): - self.handler = handler - self._addr = (host, port) - self._server = None - self._thread = None - - @property - def address(self): - host, port = self._addr - if host == '': - host = 'localhost' - return '{}:{}'.format(host, port) - - def start(self): - if self._server is not None: - raise RuntimeError('already started') - self._server = HTTPServer(self._addr, self.handler) - self._thread = new_hidden_thread( - target=(lambda: self._server.serve_forever()), - name='test.http', - ) - self._thread.start() - - def stop(self): - if self._server is None: - raise RuntimeError('not running') - self._server.shutdown() - self._thread.join() - self._server.server_close() - self._thread = None - self._server = None - - def __enter__(self): - self.start() - return self - - def __exit__(self, *args): - self.stop() - - -def json_file_handler(data): - """Return an HTTP handler that always serves the given JSON bytes.""" - - class HTTPHandler(BaseHTTPRequestHandler): - def do_GET(self): - self.send_response(200) - self.send_header('Content-Type', b'application/json') - self.send_header('Content-Length', - str(len(data)).encode('ascii')) - self.end_headers() - self.wfile.write(data) - - def log_message(self, *args, **kwargs): - pass - - return HTTPHandler - - -def error_handler(code, msg): - """Return an HTTP handler that always returns the given error code.""" - - class HTTPHandler(BaseHTTPRequestHandler): - def do_GET(self): - self.send_error(code, msg) - - def log_message(self, *args, **kwargs): - pass - - return HTTPHandler diff --git a/tests/helpers/lock.py b/tests/helpers/lock.py deleted file mode 100644 index 1ba3c98b..00000000 --- a/tests/helpers/lock.py +++ /dev/null @@ -1,105 +0,0 @@ -import inspect -import os -import os.path -import time - - -class LockTimeoutError(RuntimeError): - pass - - -################################## -# lock files - -# TODO: Support a nonce for lockfiles? - -def _acquire_lockfile(filename, timeout): - # Wait until it does not exist. - for _ in range(int(timeout * 10) + 1): - if not os.path.exists(filename): - break - time.sleep(0.1) - else: - if os.path.exists(filename): - raise LockTimeoutError( - 'timed out waiting for lockfile %r' % filename) - # Create the file. - with open(filename, 'w'): - pass - - -def _release_lockfile(filename): - try: - os.remove(filename) - except OSError: - if not os.path.exists(filename): - raise RuntimeError('lockfile not held') - # TODO: Fail here? - pass - - -_ACQUIRE_LOCKFILE = """ -# <- START ACQUIRE LOCKFILE SCRIPT -> -import os.path -import time -class LockTimeoutError(RuntimeError): - pass -%s -_acquire_lockfile({!r}, {!r}) -# <- END ACQUIRE LOCKFILE SCRIPT -> -""" % inspect.getsource(_acquire_lockfile).strip() - -_RELEASE_LOCKFILE = """ -# <- START RELEASE LOCKFILE SCRIPT -> -import os -import os.path -%s -_release_lockfile({!r}) -# <- END RELEASE LOCKFILE SCRIPT -> -""" % inspect.getsource(_release_lockfile).strip() - - -class Lockfile(object): - """A wrapper around a lock file.""" - - def __init__(self, filename): - self._filename = filename - - def __repr__(self): - return '{}(filename={!r})'.format( - type(self).__name__, - self._filename, - ) - - def __str__(self): - return self._filename - - @property - def filename(self): - return self._filename - - def acquire(self, timeout=5.0): - _acquire_lockfile(self._filename, timeout) - - def acquire_script(self, timeout=5.0): - return _ACQUIRE_LOCKFILE.format(self._filename, timeout) - - def release(self): - _release_lockfile(self._filename) - - def release_script(self): - return _RELEASE_LOCKFILE.format(self._filename) - - def wait_for_script(self): - """Return (done script, wait func) after acquiring.""" - def wait(**kwargs): - self.acquire(**kwargs) - self.release() - self.acquire() - return self.release_script(), wait - - def wait_in_script(self, **kwargs): - """Return (done func, wait script) after acquiring.""" - script = self.acquire_script(**kwargs) + self.release_script() - self.acquire() - return self.release, script diff --git a/tests/helpers/message.py b/tests/helpers/message.py deleted file mode 100644 index 17b2c8cd..00000000 --- a/tests/helpers/message.py +++ /dev/null @@ -1,129 +0,0 @@ -from . import noop -from ._io import write_all, read_buffered -from .header import read_one as read_header, write_one as write_header - - -def raw_read_all(read, initial=b'', stop=noop): - """Yield (msg, headers, remainder) for each message read.""" - headers = {} - remainder = initial - while not stop(): - header, remainder = read_header(read, initial=remainder, stop=stop) - if header is not None: - name, value = header - headers[name] = value - continue - - # end-of-headers - numbytes = int(headers['Content-Length']) - data, remainder = read_buffered(read, numbytes, initial=remainder, - stop=stop) - msg = data.decode('utf-8', 'replace') - yield msg, headers, remainder - headers = {} - - -def raw_write_one(write, body, stop=noop, **headers): - """Write the message.""" - body = body.encode('utf-8') - headers.setdefault('Content-Length', len(body)) - for name, value in headers.items(): - write_header(write, name, value, stop=stop) - write_all(write, b'\r\n') - write_all(write, body) - - -def assert_messages_equal(received, expected): - if received != expected: - try: - from itertools import zip_longest - except ImportError: - from itertools import izip_longest as zip_longest - - msg = [''] - msg.append('Received:') - for r in received: - msg.append(str(r)) - msg.append('') - - msg.append('Expected:') - for r in expected: - msg.append(str(r)) - msg.append('') - - msg.append('Diff by line') - for i, (a, b) in enumerate( - zip_longest(received, expected, fillvalue=None)): - if a == b: - msg.append(' %2d: %s' % (i, a,)) - else: - msg.append('!%2d: %s != %s' % (i, a, b)) - - raise AssertionError('\n'.join(msg)) - - -def assert_contains_messages(received, expected): - error_message = [''] - received_copy = list(msg._replace(seq=0) for msg in received) - expected_copy = list(msg._replace(seq=0) for msg in expected) - received_messages = '\nReceived:\n' + \ - '\n'.join(str(msg) for msg in received_copy) - for msg in expected_copy: - if msg in received_copy: - del received_copy[received_copy.index(msg)] - else: - error_message.append('Not found:') - error_message.append(str(msg)) - - if len(error_message) > 1: - expected_messages = '\nExpected:\n' + \ - '\n'.join(str(msg) for msg in expected_copy) - raise AssertionError('\n'.join(error_message) + - received_messages + - expected_messages) - - -def assert_is_subset(received_message, expected_message): - message = [ - 'Subset comparison failed', - 'Received: {}'.format(received_message), - 'Expected: {}'.format(expected_message), - ] - - def assert_is_subset(received, expected, current_path=''): - try: - if received == expected: - return - elif type(expected) is dict: - try: - iterator = expected.iteritems() - except AttributeError: - iterator = expected.items() - parent_path = current_path - for pkey, pvalue in iterator: - current_path = '{}.{}'.format(parent_path, pkey) - assert_is_subset(received[pkey], pvalue, current_path) - elif type(expected) is list: - parent_path = current_path - for i, pvalue in enumerate(expected): - current_path = '{}[{}]'.format(parent_path, i) - assert_is_subset(received[i], pvalue, current_path) - else: - if received != expected: - raise ValueError - return True - except ValueError: - message.append('Path: body{}'.format(current_path)) - message.append('Received:{}'.format(received)) - message.append('Expected:{}'.format(expected)) - raise AssertionError('\n'.join(message)) - except KeyError: - message.append('Key not found: body{}'.format(current_path)) - raise AssertionError('\n'.join(message)) - except IndexError: - message.append('Index not found: body'.format(current_path)) - raise AssertionError('\n'.join(message)) - - received = received_message.body if hasattr(received_message, 'body') else received_message # noqa - expected = expected_message.body if hasattr(expected_message, 'body') else expected_message # noqa - assert_is_subset(received, expected) diff --git a/pytests/helpers/messaging.py b/tests/helpers/messaging.py similarity index 100% rename from pytests/helpers/messaging.py rename to tests/helpers/messaging.py diff --git a/pytests/helpers/pathutils.py b/tests/helpers/pathutils.py similarity index 87% rename from pytests/helpers/pathutils.py rename to tests/helpers/pathutils.py index 5d2f1b42..e6b6f964 100644 --- a/pytests/helpers/pathutils.py +++ b/tests/helpers/pathutils.py @@ -9,8 +9,8 @@ import ptvsd.compat def get_test_root(name): - pytests_dir = os.path.dirname(os.path.dirname(__file__)) - p = os.path.join(pytests_dir, 'func', 'testfiles', name) + tests_dir = os.path.dirname(os.path.dirname(__file__)) + p = os.path.join(tests_dir, 'func', 'testfiles', name) if os.path.exists(p): return p return None diff --git a/pytests/helpers/pattern.py b/tests/helpers/pattern.py similarity index 98% rename from pytests/helpers/pattern.py rename to tests/helpers/pattern.py index b1ffa8b9..51ae51c3 100644 --- a/pytests/helpers/pattern.py +++ b/tests/helpers/pattern.py @@ -7,7 +7,7 @@ from __future__ import print_function, with_statement, absolute_import import numbers from ptvsd.compat import unicode -from pytests.helpers.pathutils import compare_path +from tests.helpers.pathutils import compare_path class BasePattern(object): diff --git a/pytests/helpers/printer.py b/tests/helpers/printer.py similarity index 95% rename from pytests/helpers/printer.py rename to tests/helpers/printer.py index 73a9b32f..ae2bd6a1 100644 --- a/pytests/helpers/printer.py +++ b/tests/helpers/printer.py @@ -8,7 +8,7 @@ __all__ = ['print', 'wait_for_output'] import threading from ptvsd.compat import queue -from pytests.helpers import timestamp, colors +from tests.helpers import timestamp, colors real_print = print diff --git a/tests/helpers/proc.py b/tests/helpers/proc.py deleted file mode 100644 index 6a7da923..00000000 --- a/tests/helpers/proc.py +++ /dev/null @@ -1,241 +0,0 @@ -from __future__ import absolute_import - -import os -try: - import queue -except ImportError: - import Queue as queue # Python 2.7 -import subprocess -import sys -import time - -from ptvsd._util import new_hidden_thread, Closeable - - -_NOT_SET = object() - - -def process_lines(stream, notify_received, notify_done=None, check_done=None, - close=True): - # (inspired by https://stackoverflow.com/questions/375427) - if check_done is None: - check_done = (lambda: False) - line = stream.readline() - while line and not check_done(): # Break on EOF. - notify_received(line) - try: - line = stream.readline() - except ValueError: # stream closed - line = '' - if notify_done is not None: - notify_done() - if close: - # TODO: What if stream doesn't have close()? - stream.close() - - -def collect_lines(stream, buf=None, notify_received=None, **kwargs): - # (inspired by https://stackoverflow.com/questions/375427) - if buf is None: - buf = queue.Queue() - - if notify_received is None: - notify_received = buf.put - else: - def notify_received(line, _notify=notify_received): - _notify(line) - buf.put(line) - - t = new_hidden_thread( - target=process_lines, - args=(stream, notify_received), - kwargs=kwargs, - name='test.proc.output', - ) - t.start() - - return buf, t - - -class TimeoutException(Exception): - def __init__(self, *args, **kwargs): - Exception.__init__(self, *args, **kwargs) - - -class ProcOutput(object): - """A tracker for a process's std* output.""" - - # TODO: Support stderr? - # TODO: Support buffer max size? - # TODO: Support cache max size? - - def __init__(self, proc): - if proc.stdout is None: - raise ValueError('proc.stdout is None') - - self._proc = proc - self._output = b'' - - def notify_received(line): - self._output += line - self._buffer, _ = collect_lines( - proc.stdout, - notify_received=notify_received, - ) - - def __str__(self): - self._flush() - return self._output.decode('utf-8') - - def __bytes__(self): - self._flush() - return self._output - - def __iter__(self): - return self - - def __next__(self): - while True: - try: - return self._buffer.get(timeout=0.01) - except queue.Empty: - if self._proc.poll() is not None: - raise StopIteration - - next = __next__ # for Python 2.7 - - def readline(self): - try: - self._buffer.get_nowait() - except queue.Empty: - return b'' - - def decode(self, *args, **kwargs): - self._flush() - return self._output.decode(*args, **kwargs) - - def reset(self): - # TODO: There's a small race here. - self._flush() - self._output = b'' - - # internal methods - - def _flush(self): - for _ in range(self._buffer.qsize()): - try: - self._buffer.get_nowait() - except queue.Empty: - break - - -class Proc(Closeable): - """A wrapper around a subprocess.Popen object.""" - - VERBOSE = False - #VERBOSE = True - - ARGV = [ - sys.executable, - '-u', # stdout/stderr unbuffered - ] - - @classmethod - def start_python_script(cls, filename, argv, **kwargs): - argv = list(cls.ARGV) + [ - filename, - ] + argv - return cls.start(argv, **kwargs) - - @classmethod - def start_python_module(cls, module, argv, **kwargs): - argv = list(cls.ARGV) + [ - '-m', module, - ] + argv - return cls.start(argv, **kwargs) - - @classmethod - def start(cls, argv, env=None, cwd=None, stdout=_NOT_SET, stderr=_NOT_SET): - if env is None: - env = {} - if cls.VERBOSE: - env.setdefault('PTVSD_DEBUG', '1') - proc = cls._start(argv, env, cwd, stdout, stderr) - return cls(proc, owned=True) - - @classmethod - def _start(cls, argv, env, cwd, stdout, stderr): - if stdout is _NOT_SET: - stdout = subprocess.PIPE - if stderr is _NOT_SET: - stderr = subprocess.STDOUT - if env is not None: - env_copy = os.environ.copy() - env_copy.update(env) - env = env_copy - proc = subprocess.Popen( - argv, - stdout=stdout, - stderr=stderr, - #close_fds=('posix' in sys.builtin_module_names), - env=env, - cwd=cwd - ) - return proc - - def __init__(self, proc, owned=False): - super(Proc, self).__init__() - assert isinstance(proc, subprocess.Popen) - self._proc = proc - if proc.stdout is sys.stdout or proc.stdout is None: - self._output = None - else: - self._output = ProcOutput(proc) - - # TODO: Emulate class-only methods? - #def __getattribute__(self, name): - # val = super(Proc, self).__getattribute__(name) - # if isinstance(type(self).__dict__.get(name), classmethod): - # raise AttributeError(name) - # return val - - @property - def pid(self): - return self._proc.pid - - @property - def output(self): - return self._output - - @property - def exitcode(self): - # TODO: Use proc.poll()? - return self._proc.returncode - - def wait(self, timeout=5.0): - start_time = time.time() - while self._proc.poll() is None: - time.sleep(0.1) - if time.time() - start_time > timeout: - raise TimeoutException('Timeout waiting for process to die') - - # internal methods - - def _close(self): - self.terminate() - if self.VERBOSE: - out = self.output - if out is not None: - lines = out.decode('utf-8').splitlines() - print(' + ' + '\n + '.join(lines)) - - def terminate(self): - if self._proc is not None: - try: - self._proc.kill() - except OSError: - # Already killed. - pass - else: - if self.VERBOSE: - print('proc killed') diff --git a/tests/helpers/protocol.py b/tests/helpers/protocol.py deleted file mode 100644 index 2968a983..00000000 --- a/tests/helpers/protocol.py +++ /dev/null @@ -1,402 +0,0 @@ -from __future__ import absolute_import - -from collections import namedtuple -import contextlib -import errno -import threading -import warnings - -from ptvsd._util import new_hidden_thread -from . import socket -from .counter import Counter -from .threading import acquire_with_timeout - - -try: - BrokenPipeError -except NameError: - class BrokenPipeError(Exception): - pass - - -class StreamFailure(Exception): - """Something went wrong while handling messages to/from a stream.""" - - def __init__(self, direction, msg, exception): - err = 'error while processing stream: {!r}'.format(exception) - super(StreamFailure, self).__init__(self, err) - self.direction = direction - self.msg = msg - self.exception = exception - - def __repr__(self): - return '{}(direction={!r}, msg={!r}, exception={!r})'.format( - type(self).__name__, - self.direction, - self.msg, - self.exception, - ) - - -class MessageProtocol(namedtuple('Protocol', 'parse encode iter')): - """A basic abstraction of a message protocol. - - parse(msg) - returns a message for the given data. - encode(msg) - returns the message, serialized to the line-format. - iter(stream, stop) - yield each message from the stream. "stop" - is a function called with no args which returns True if the - iterator should stop. - """ - - def parse_each(self, messages): - """Yield the parsed version of each message.""" - for msg in messages: - yield self.parse(msg) - - -class MessageCounters(namedtuple('MessageCounters', - 'request response event')): - """Track the next "seq" value for the protocol message types.""" - - REQUEST_INC = 1 - RESPONSE_INC = 1 - EVENT_INC = 1 - - def __new__(cls, request=0, response=0, event=None): - request = Counter(request, cls.REQUEST_INC) - if response is None: - response = request - else: - response = Counter(response, cls.RESPONSE_INC) - if event is None: - event = response - else: - event = Counter(event, cls.EVENT_INC) - self = super(MessageCounters, cls).__new__( - cls, - request, - response, - event, - ) - return self - - def next_request(self): - return next(self.request) - - def next_response(self): - return next(self.response) - - def next_event(self): - return next(self.event) - - def reset(self, request=None, response=None, event=None): - if request is None and response is None and event is None: - raise ValueError('missing at least one counter') - if request is not None: - self.request.reset(start=request) - if response is not None: - self.response.reset(start=response) - if event is not None: - self.event.reset(start=event) - - def reset_all(self, start=0): - self.request.reset(start) - self.response.reset(start) - self.event.reset(start) - - -class DaemonStarted(object): - """A simple wrapper around a started protocol daemon.""" - - def __init__(self, daemon, address, starting=None): - self.daemon = daemon - self.address = address - self._starting = starting - - def __enter__(self): - self.wait_until_connected() - return self - - def __exit__(self, *args): - self.close() - - def wait_until_connected(self, timeout=None): - starting = self._starting - if starting is None: - return - starting.join(timeout=timeout) - if starting.is_alive(): - raise RuntimeError('timed out') - self._starting = None - - def close(self): - self.wait_until_connected() - self.daemon.close() - - -class Daemon(object): - - STARTED = DaemonStarted - - def __init__(self, bind): - self._bind = bind - - self._closed = False - - # These are set when we start. - self._address = None - self._sock = None - - def start(self, address): - """Start the fake daemon. - - This calls the earlier provided bind() function. - - A listener loop is started in another thread to handle incoming - messages from the socket. - """ - self._address = address - addr, starting = self._start(address) - return self.STARTED(self, addr, starting) - - def close(self): - """Clean up the daemon's resources (e.g. sockets, files, listener).""" - if self._closed: - return - - self._closed = True - self._close() - - # internal methods - - def _start(self, address): - connect, addr = self._bind(address) - - def run(): - self._sock = connect() - self._handle_connected() - t = new_hidden_thread( - target=run, - name='test.daemon', - ) - t.start() - return addr, t - - def _handle_connected(self): - pass - - def _close(self): - if self._sock is not None: - socket.close(self._sock) - self._sock = None - - -class MessageDaemonStarted(DaemonStarted): - """A simple wrapper around a started message protocol daemon.""" - - def send_message(self, msg): - self.wait_until_connected() - return self.daemon.send_message(msg) - - -class MessageDaemon(Daemon): - """A testing double for a protocol daemon.""" - - STARTED = MessageDaemonStarted - - EXTERNAL = None - PRINT_SENT_MESSAGES = False - PRINT_RECEIVED_MESSAGES = False - - @classmethod - def validate_message(cls, msg): - """Ensure the message is legitimate.""" - # By default check nothing. - - def __init__(self, bind, protocol, handler): - super(MessageDaemon, self).__init__(bind) - - self._protocol = protocol - - self._received = [] - self._failures = [] - - self._handlers = [] - self._default_handler = handler - - # These are set when we start. - self._listener = None - - @property - def protocol(self): - return self._protocol - - @property - def received(self): - """All the messages received thus far.""" - #parsed = self._protocol.parse_each(self._received) - #return list(parsed) - return list(self._received) - - @property - def failures(self): - """All send/recv failures thus far.""" - return list(self._failures) - - def send_message(self, msg): - """Serialize msg to the line format and send it to the socket.""" - if self._closed: - raise EOFError('closed') - self._validate_message(msg) - self._send_message(msg) - - def add_handler(self, handler, handlername=None, caller=None, oneoff=True): - """Add the given handler to the list of possible handlers.""" - entry = ( - handler, - handlername or repr(handler), - caller, - 1 if oneoff else None, - ) - self._handlers.append(entry) - return handler - - @contextlib.contextmanager - def wait_for_message(self, match, req=None, handler=None, - handlername=None, caller=None, timeout=1): - """Return a context manager that will wait for a matching message.""" - lock = threading.Lock() - lock.acquire() - - def handle_message(msg, send_message): - if not match(msg): - return False - lock.release() - if handler is not None: - handler(msg, send_message) - return True - self.add_handler(handle_message, handlername, caller) - - yield req - - # Wait for the message to match. - if acquire_with_timeout(lock, timeout=timeout): - lock.release() - else: - msg = 'timed out after {} seconds waiting for message ({})' - warnings.warn(msg.format(timeout, handlername)) - - def reset(self, *initial, **kwargs): - """Clear the recorded messages.""" - self._reset(initial, **kwargs) - - # internal methods - - def _handle_connected(self): - # TODO: make it a daemon thread? - self._listener = new_hidden_thread( - target=self._listen, - name='test.msgdaemon', - daemon=False, - ) - self._listener.start() - - def _listen(self): - try: - with contextlib.closing(self._sock.makefile('rb')) as sockfile: - for msg in self._protocol.iter(sockfile, lambda: self._closed): - if isinstance(msg, StreamFailure): - self._failures.append(msg) - else: - self._add_received(msg) - except BrokenPipeError: - if self._closed: - return - # TODO: try reconnecting? - raise - except OSError as exc: - if exc.errno in (errno.EPIPE, errno.ESHUTDOWN): # BrokenPipeError - return - if exc.errno == 9: # socket closed - return - if exc.errno == errno.EBADF: # socket closed - return - # TODO: try reconnecting? - raise - - def _add_received(self, msg): - if self.PRINT_RECEIVED_MESSAGES: - print('<--' if self.EXTERNAL else '-->', msg) - self._received.append(msg) - self._handle_message(msg) - - def _handle_message(self, msg): - for i, entry in enumerate(list(self._handlers)): - handle_msg, name, caller, remaining = entry - handled = handle_msg(msg, self._send_message) - if handled or handled is None: - if remaining is not None: - if remaining == 1: - self._handlers.pop(i) - else: - self._handlers[i] = (handle_msg, name, caller, - remaining-1) - return handled - else: - if self._default_handler is not None: - return self._default_handler(msg, self._send_message) - return False - - def _validate_message(self, msg): - return - - def _send_message(self, msg): - if self.PRINT_SENT_MESSAGES: - print('-->' if self.EXTERNAL else '<--', msg) - msg = self._protocol.parse(msg) - raw = self._protocol.encode(msg) - try: - self._send(raw) - except Exception as exc: - raise - failure = StreamFailure('send', msg, exc) - self._failures.append(failure) - - def _send(self, raw): - while raw: - sent = self._sock.send(raw) - raw = raw[sent:] - - def _close(self): - super(MessageDaemon, self)._close() - if self._listener is not None: - self._listener.join(timeout=1) - # TODO: the listener isn't stopping! - #if self._listener.is_alive(): - # raise RuntimeError('timed out') - self._listener = None - - def _reset(self, initial, force=False): - if self._failures: - raise RuntimeError('have failures ({!r})'.format(self._failures)) - if self._handlers: - if force: - self._handlers = [] - else: - names = [] - for _, name, caller, _ in self._handlers: - if caller: - try: - filename, lineno = caller - except (ValueError, TypeError): - # TODO: Support str, tracebacks? - raise NotImplementedError - else: - caller = '{}, line {}'.format(filename, lineno) - names.append('{} ({})'.format(name, caller)) - - else: - names.append(name) - names = ', '.join(names) - raise RuntimeError('have pending handlers: [{}]'.format(names)) - self._received = list(self._protocol.parse_each(initial)) diff --git a/tests/helpers/pydevd/__init__.py b/tests/helpers/pydevd/__init__.py deleted file mode 100644 index 4cbc8eb9..00000000 --- a/tests/helpers/pydevd/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from ._fake import FakePyDevd # noqa -from ._messages import PyDevdMessages # noqa -from ._pydevd import RawMessage # noqa diff --git a/tests/helpers/pydevd/_binder.py b/tests/helpers/pydevd/_binder.py deleted file mode 100644 index ae178839..00000000 --- a/tests/helpers/pydevd/_binder.py +++ /dev/null @@ -1,205 +0,0 @@ -import threading -import time - -import ptvsd.daemon -from tests.helpers import socket -from tests.helpers.threading import acquire_with_timeout - - -class PTVSD(ptvsd.daemon.Daemon): - """A wrapper around a running "instance" of PTVSD. - - "client" and "server" are the two ends of socket that PTVSD uses - to communicate with the editor (e.g. VSC) via the VSC debug adapter - protocol. "server" will be None for a remote address. - "proc" is the wrapper around the VSC message handler. - "fakesock" is the socket-like object that PTVSD uses to communicate - with the debugger (e.g. PyDevd) via the PyDevd wire protocol. - """ - - @classmethod - def from_connect_func(cls, connect, singlesession=None): - """Return a new instance using the socket returned by connect().""" - client, server = connect() - if singlesession is None: - singlesession = (server is None) - self = cls( - wait_for_user=(lambda: None), - addhandlers=False, - killonclose=False, - singlesession=singlesession, - ) - self.start() - self.start_session(client, 'ptvsd.Server') - self.server = server - return self - - @property - def fakesock(self): - return self.pydevd - - @property - def proc(self): - if self.session is None: - return None - return self.session.msgprocessor - - def close(self): - """Stop PTVSD and clean up. - - This will trigger the VSC protocol end-of-debugging message flow - (e.g. "exited" and "terminated" events). As part of that flow - this function may block while waiting for specific messages from - the editor (e.g. a "disconnect" request). PTVSD also closes all - of its background threads and closes any sockets it controls. - """ - try: - super(PTVSD, self).close() - except ptvsd.daemon.DaemonClosedError: - pass - - -class BinderBase(object): - """Base class for one-off socket binders (for protocol daemons). - - A "binder" facilitates separating the socket-binding behavior from - the socket-connecting behavior. This matters because for server - sockets the connecting part is a blocking operation. - - The bind method may be passed to protocol.Daemon() as the "bind" - argument. - - Note that a binder starts up ptvsd using the connected socket and - runs the debugger in the background. - """ - - def __init__(self, address=None, ptvsd=None, singlesession=None): - if address is not None or ptvsd is not None: - raise NotImplementedError - - self.singlesession = singlesession - - # Set when bind() called: - self.address = None - self._connect = None - self._waiter = None - - # Set when ptvsd started: - self._thread = None - self.ptvsd = None - - def __repr__(self): - return '{}(address={!r}, ptvsd={!r})'.format( - type(self).__name__, - self.address, - self.ptvsd, - ) - - @property - def thread(self): - return self._thread - - def bind(self, address): - """Return (connect func, remote addr) after binding a socket. - - A new client or server socket is immediately bound, depending on - the address. Then the connect func is generated for that - socket. The func takes no args and returns a client socket - connected to the original address. In the case of a remote - address, that socket may be the one that was originally bound. - - When the connect func is called, PTVSD is started up using the - socket. Then some debugging operation (e.g. running a script - through pydevd) is started in a background thread. - """ - if self._connect is not None: - raise RuntimeError('already bound') - self.address = address - self._connect, remote = socket.bind(address) - self._waiter = threading.Lock() - self._waiter.acquire() - - def connect(): - if self._thread is not None: - raise RuntimeError('already connected') - # We do not give this thread a name because we actually do - # want ptvsd to track it. - self._thread = threading.Thread(target=self._run) - self._thread.start() - # Wait for ptvsd to start up. - if acquire_with_timeout(self._waiter, timeout=1): - self._waiter.release() - else: - raise RuntimeError('timed out') - return self._wrap_sock() - - return connect, remote - - def wait_until_done(self, timeout=10.0): - """Wait for the started debugger operation to finish.""" - if self._thread is None: - return - self._thread.join(timeout) - if self._thread.isAlive(): - raise Exception( - 'wait_until_done timed out after {} secs'.format(timeout)) - - #################### - # for subclassing - - def _run_debugger(self): - # Subclasses import this. The method must directly or - # indirectly call self._start_ptvsd(). - raise NotImplementedError - - def _wrap_sock(self): - return socket.Connection(self.ptvsd.session.socket, self.ptvsd.server) - - #################### - # internal methods - - def _start_ptvsd(self): - if self.ptvsd is not None: - raise RuntimeError('already connected') - self.ptvsd = PTVSD.from_connect_func( - self._connect, - singlesession=self.singlesession, - ) - self._waiter.release() - - def _run(self): - try: - close = self._run_debugger() - except SystemExit as exc: - self.ptvsd.exitcode = int(exc.code) - raise - self.ptvsd.exitcode = 0 - if close or close is None: - self.ptvsd.close() - - -class Binder(BinderBase): - """A "binder" that defers the debugging operation to an external func. - - That function takes two arguments, "external" and "internal", and - returns nothing. "external" is a socket that an editor (or fake) - may use to communicate with PTVSD over the VSC debug adapter - protocol. "internal does the same for a debugger and the PyDevd - wire protocol. The function should exit once debugging has - finished. - """ - - def __init__(self, do_debugging=None, **kwargs): - if do_debugging is None: - - def do_debugging(external, internal): - time.sleep(5) - - super(Binder, self).__init__(**kwargs) - self._do_debugging = do_debugging - - def _run_debugger(self): - self._start_ptvsd() - external = self.ptvsd.session.server - internal = self.ptvsd.fakesock - self._do_debugging(external, internal) diff --git a/tests/helpers/pydevd/_fake.py b/tests/helpers/pydevd/_fake.py deleted file mode 100644 index 933493e8..00000000 --- a/tests/helpers/pydevd/_fake.py +++ /dev/null @@ -1,177 +0,0 @@ -import contextlib -import threading - -from _pydevd_bundle.pydevd_comm import ( - CMD_VERSION, -) - -from ._pydevd import parse_message, encode_message, iter_messages, Message -from tests.helpers import protocol, socket -from ._binder import BinderBase - - -PROTOCOL = protocol.MessageProtocol( - parse=parse_message, - encode=encode_message, - iter=iter_messages, -) - - -class Binder(BinderBase): - - def __init__(self, singlesession=True): - super(Binder, self).__init__( - singlesession=singlesession, - ) - self._lock = threading.Lock() - self._lock.acquire() - - def _run_debugger(self): - self._start_ptvsd() - # Block until "done" debugging. - self._lock.acquire() - - def _wrap_sock(self): - return socket.Connection(self.ptvsd.fakesock, self.ptvsd.server) - - def _done(self): - self._lock.release() - - -class Started(protocol.MessageDaemonStarted): - - def send_response(self, msg): - self.wait_until_connected() - return self.daemon.send_response(msg) - - def send_event(self, msg): - self.wait_until_connected() - return self.daemon.send_event(msg) - - -class FakePyDevd(protocol.MessageDaemon): - """A testing double for PyDevd. - - Note that you have the option to provide a handler function. This - function will be called for each received message, with two args: - the received message and the fake's "send_message" method. If - appropriate, it may call send_message() in response to the received - message, along with doing anything else it needs to do. Any - exceptions raised by the handler are recorded but otherwise ignored. - - Example usage: - - >>> fake = FakePyDevd('127.0.0.1', 8888) - >>> with fake.start('127.0.0.1', 8888): - ... fake.send_response(b'101\t1\t') - ... fake.send_event(b'900\t2\t') - ... - >>> fake.assert_received(testcase, [ - ... b'101\t1\t', # the "run" request - ... # some other requests - ... ]) - >>> - - A description of the protocol: - https://github.com/fabioz/PyDev.Debugger/blob/master/_pydevd_bundle/pydevd_comm.py - """ # noqa - - STARTED = Started - EXTERNAL = False - - PROTOCOL = PROTOCOL - VERSION = '1.1.1' - - @classmethod - def validate_message(cls, msg): - """Ensure the message is legitimate.""" - # TODO: Check the message. - - @classmethod - def handle_request(cls, req, send_message, handler=None): - """The default message handler.""" - if handler is not None: - handler(req, send_message) - - resp = cls._get_response(req) - if resp is not None: - send_message(resp) - - @classmethod - def _get_response(cls, req): - try: - cmdid, seq, _ = req - except (IndexError, ValueError): - req = req.msg - cmdid, seq, _ = req - - if cmdid == CMD_VERSION: - return Message(CMD_VERSION, seq, cls.VERSION) - else: - return None - - def __init__(self, handler=None, **kwargs): - self.binder = Binder(**kwargs) - - super(FakePyDevd, self).__init__( - self.binder.bind, - PROTOCOL, - (lambda msg, send: self.handle_request(msg, send, handler)), - ) - - @contextlib.contextmanager - def wait_for_command(self, cmdid, seq=None, **kwargs): - def match(msg): - #msg = parse_message(msg) - try: - actual = msg.cmdid - except AttributeError: - return False - if actual != cmdid: - return False - if seq is not None: - try: - actual = msg.seq - except AttributeError: - return False - if actual != seq: - return False - return True - with self.wait_for_message(match, req=None, **kwargs): - yield - - def send_response(self, msg): - """Send a response message to the adapter (ptvsd).""" - # XXX Ensure it's a response? - return self._send_message(msg) - - def send_event(self, msg): - """Send an event message to the adapter (ptvsd).""" - # XXX Ensure it's a request? - return self.send_message(msg) - - def add_pending_response(self, cmdid, text, reqid=None, handlername=None): - """Add a response for a request.""" - if reqid is None: - reqid = cmdid - respid = cmdid - - if handlername is None: - handlername = ''.format(cmdid) - - def handle_request(req, send_message): - try: - cmdid, seq, _ = req - except (IndexError, ValueError): - req = req.msg - cmdid, seq, _ = req - if cmdid != reqid: - return False - resp = Message(respid, seq, text) - send_message(resp) - return True - self.add_handler(handle_request, handlername) - - def _close(self): - self.binder._done() - super(FakePyDevd, self)._close() diff --git a/tests/helpers/pydevd/_live.py b/tests/helpers/pydevd/_live.py deleted file mode 100644 index de2b4963..00000000 --- a/tests/helpers/pydevd/_live.py +++ /dev/null @@ -1,94 +0,0 @@ -import os -import os.path -import threading -import warnings - -import ptvsd._local -from tests.helpers import protocol -from tests.helpers.threading import acquire_with_timeout -from ._binder import BinderBase - - -class Binder(BinderBase): - - def __init__(self, filename, module, singlesession=True, **kwargs): - super(Binder, self).__init__( - singlesession=singlesession, - **kwargs - ) - self.filename = filename - self.module = module - self._lock = threading.Lock() - self._lock.acquire() - self._closeondone = True - - def _run_debugger(self): - def new_pydevd_sock(*args): - self._start_ptvsd() - return self.ptvsd.fakesock - if self.module is None: - run = ptvsd._local.run_file - name = self.filename - else: - run = ptvsd._local.run_module - name = self.module - run( - self.address, - name, - start_server=new_pydevd_sock, - start_client=new_pydevd_sock, - wait_for_user=(lambda: None), - addhandlers=False, - killonclose=False, - ) - - # Block until "done" debugging. - if not acquire_with_timeout(self._lock, timeout=3): - # This shouldn't happen since the timeout on event waiting - # is this long. - warnings.warn('timeout out waiting for "done"') - return self._closeondone - - def done(self, close=True): - self._closeondone = close - self._lock.release() - - -class LivePyDevd(protocol.Daemon): - - @classmethod - def parse_source(cls, source): - kind, sep, name = source.partition(':') - if kind == 'file': - return name, None, False - elif kind == 'module': - parts = (name).split('.') - filename = os.path.join(*parts) + '.py' - return filename, name, False - else: - # TODO: Write source code to temp module? - raise NotImplementedError - - def __init__(self, source, **kwargs): - filename, module, owned = self.parse_source(source) - self._filename = filename - self._owned = owned - self.binder = Binder(filename, module, **kwargs) - - super(LivePyDevd, self).__init__(self.binder.bind) - - @property - def thread(self): - return self.binder.thread - - def _close(self): - # Note that we do not call self.binder.done() here, though it - # might make sense as a fallback. Instead, we do so directly - # in the relevant test cases. - super(LivePyDevd, self)._close() - # TODO: Close pydevd somehow? - - if self._owned: - os.unlink(self._filename) - if self.binder.ptvsd is not None: - self.binder.ptvsd.close() diff --git a/tests/helpers/pydevd/_messages.py b/tests/helpers/pydevd/_messages.py deleted file mode 100644 index ecc90d4e..00000000 --- a/tests/helpers/pydevd/_messages.py +++ /dev/null @@ -1,134 +0,0 @@ -try: - import urllib.parse as urllib -except ImportError: - import urllib - -from _pydevd_bundle import pydevd_xml -from _pydevd_bundle.pydevd_comm import ( - CMD_SEND_CURR_EXCEPTION_TRACE, -) - -from tests.helpers.protocol import MessageCounters -from ._fake import FakePyDevd - - -class PyDevdMessages(object): - - protocol = FakePyDevd.PROTOCOL - - def __init__(self, - request_seq=1000000000, # ptvsd requests to pydevd - response_seq=0, # PyDevd responses/events to ptvsd - event_seq=None, - ): - self.counters = MessageCounters( - request_seq, - response_seq, - event_seq, - ) - - def __getattr__(self, name): - return getattr(self.counters, name) - - def new_request(self, cmdid, *args, **kwargs): - """Return a new PyDevd request message.""" - seq = kwargs.pop('seq', None) - if seq is None: - seq = self.counters.next_request() - return self._new_message(cmdid, seq, args, **kwargs) - - def new_response(self, req, *args): - """Return a new VSC response message.""" - #seq = kwargs.pop('seq', None) - #if seq is None: - # seq = next(self.response_seq) - req = self.protocol.parse(req) - return self._new_message(req.cmdid, req.seq, args) - - def new_event(self, cmdid, *args, **kwargs): - """Return a new VSC event message.""" - seq = kwargs.pop('seq', None) - if seq is None: - seq = self.counters.next_event() - return self._new_message(cmdid, seq, args, **kwargs) - - def _new_message(self, cmdid, seq, args=()): - text = '\t'.join(args) - msg = (cmdid, seq, text) - return self.protocol.parse(msg) - - def format_threads(self, *threads): - text = '' - for thread in threads: # (tid, tname) - text += ''.format(*thread) - text += '' - return text - - def format_frames(self, threadid, reason, *frames): - text = '' - text += ''.format(threadid, reason) - fmt = '' - for frame in frames: # (fid, func, filename, line) - text += fmt.format(*frame) - text += '' - text += '' - return text - - def format_frames2(self, threadid, reason, *frames): - return '{ "thread_id": "%s", "stop_reason": %d }' % (threadid, reason) - - def format_variables(self, *variables): - text = '' - for name, value in variables: - if isinstance(value, str) and value.startswith('err:'): - value = pydevd_xml.ExceptionOnEvaluate(value[4:]) - text += pydevd_xml.var_to_xml(value, name) - text += '' - return urllib.quote(text) - - def format_exception(self, threadid, exc, frame): - frameid, _, _, _ = frame - name = pydevd_xml.make_valid_xml_value(type(exc).__name__) - description = pydevd_xml.make_valid_xml_value(str(exc)) - - info = '' - info += ''.format(threadid) - info += '' - return '{}\t{}\t{}\t{}'.format( - frameid, - name or 'exception: type unknown', - description or 'exception: no description', - self.format_frames( - threadid, - CMD_SEND_CURR_EXCEPTION_TRACE, - frame, - ), - ) - - def format_exception_details(self, threadid, exc, *frames): - name = pydevd_xml.make_valid_xml_value(str(type(exc))) - if hasattr(exc, 'args') and len(exc.args) > 0: - desc = str(exc.args[0]) - else: - desc = str(exc) - desc = pydevd_xml.make_valid_xml_value(desc) - info = '' - info += ''.format( - threadid, name, desc) - fmt = '' - for frame in frames: # (fid, func, filename, line) - info += fmt.format(*frame) - info += '' - return info - - def format_breakpoint_exception(self, threadid, exc_type, stacktrace): - info = '' - for filename, line, methodname, methodobj in stacktrace: - fn = pydevd_xml.make_valid_xml_value(filename) - mn = pydevd_xml.make_valid_xml_value(methodname) - obj = pydevd_xml.make_valid_xml_value(methodobj) - info += '' \ - % (threadid, fn, line, mn, obj) - info += "" - return exc_type + '\t' + info - diff --git a/tests/helpers/pydevd/_pydevd.py b/tests/helpers/pydevd/_pydevd.py deleted file mode 100644 index c9121ba0..00000000 --- a/tests/helpers/pydevd/_pydevd.py +++ /dev/null @@ -1,159 +0,0 @@ -from collections import namedtuple -import sys -try: - from urllib.parse import quote, unquote -except ImportError: - from urllib import quote, unquote - -from _pydevd_bundle import pydevd_comm - -from tests.helpers.protocol import StreamFailure - -# TODO: Everything here belongs in a proper pydevd package. - - -if sys.version_info[0] > 2: - basestring = str - - -def parse_message(msg): - """Return a message object for the given "msg" data.""" - if type(msg) is bytes: - return Message.from_bytes(msg) - elif isinstance(msg, str): - return Message.from_bytes(msg) - elif type(msg) is RawMessage: - return msg.msg - elif type(msg) is Message: - return msg - elif isinstance(msg, tuple): - return Message(*msg) - else: - raise NotImplementedError - - -def encode_message(msg): - """Return the message, serialized to the line-format.""" - raw = msg.as_bytes() - if not raw.endswith(b'\n'): - raw += b'\n' - return raw - - -def iter_messages(stream, stop=lambda: False): - """Yield the correct message for each line-formatted one found.""" - lines = iter(stream) - while not stop(): - # TODO: Loop with a timeout instead of waiting indefinitely on recv(). - try: - line = next(lines) - if not line.strip(): - continue - yield parse_message(line) - except Exception as exc: - yield StreamFailure('recv', None, exc) - - -class RawMessage(namedtuple('RawMessage', 'bytes')): - """A pydevd message class that leaves the raw bytes unprocessed.""" - - @classmethod - def from_bytes(cls, raw): - """Return a RawMessage corresponding to the given raw message.""" - return cls(raw) - - def __new__(cls, raw): - if type(raw) is cls: - return raw - if type(raw) is not bytes: - raw = raw.encode('utf-8') - raw = raw.rstrip(b'\n') - self = super(RawMessage, cls).__new__(cls, raw) - return self - - @property - def msg(self): - try: - return self._msg - except AttributeError: - self._msg = Message.from_bytes(self.bytes) - return self._msg - - def as_bytes(self): - """Return the line-formatted bytes corresponding to the message.""" - return self.bytes - - -class CMDID(int): - """A PyDevd command ID.""" - - @classmethod - def from_raw(cls, raw): - if isinstance(raw, cls): - return raw - else: - return cls(raw) - - def __repr__(self): - return '<{} {}>'.format(self.name, self) - - @property - def name(self): - return pydevd_comm.ID_TO_MEANING.get(str(self), '???') - - -class Message(namedtuple('Message', 'cmdid seq payload')): - """A de-seralized PyDevd message.""" - - @classmethod - def from_bytes(cls, raw): - """Return a RawMessage corresponding to the given raw message.""" - raw = RawMessage.from_bytes(raw) - parts = raw.bytes.split(b'\t', 2) - return cls(*parts) - - @classmethod - def parse_payload(cls, payload): - """Return the de-serialized payload.""" - if isinstance(payload, bytes): - payload = payload.decode('utf-8') - if isinstance(payload, basestring): - text = unquote(payload) - return cls._parse_payload_text(text) - elif hasattr(payload, 'as_text'): - return payload - else: - raise ValueError('unsupported payload {!r}'.format(payload)) - - @classmethod - def _parse_payload_text(cls, text): - # TODO: convert to the appropriate payload type. - return text - - def __new__(cls, cmdid, seq, payload): - if cmdid or cmdid == 0: - cmdid = CMDID.from_raw(cmdid) - else: - cmdid = None - seq = int(seq) if seq or seq == 0 else None - payload = cls.parse_payload(payload) - self = super(Message, cls).__new__(cls, cmdid, seq, payload) - return self - - def __init__(self, *args, **kwargs): - if self.cmdid is None: - raise TypeError('missing cmdid') - if self.seq is None: - raise TypeError('missing seq') - - def as_bytes(self): - """Return the line-formatted bytes corresponding to the message.""" - try: - payload_as_text = self.payload.as_text - except AttributeError: - text = self.payload - else: - text = payload_as_text() - payload = quote(text) - data = '{}\t{}\t{}'.format(self.cmdid, self.seq, payload) - return data.encode('utf-8') diff --git a/tests/helpers/resource.py b/tests/helpers/resource.py deleted file mode 100644 index fa4f781f..00000000 --- a/tests/helpers/resource.py +++ /dev/null @@ -1,19 +0,0 @@ -import os.path - -from tests import RESOURCES_ROOT -from .workspace import ReadonlyFSTree - - -class TestResources(ReadonlyFSTree): - - @classmethod - def from_module(cls, modname): - parts = modname.split('.') - assert parts and parts[0] == 'tests' - root = os.path.join(RESOURCES_ROOT, *parts[1:]) - return cls(root) - - def __init__(self, root): - root = os.path.abspath(root) - assert root.startswith(RESOURCES_ROOT) - super(TestResources, self).__init__(root) diff --git a/tests/helpers/script.py b/tests/helpers/script.py deleted file mode 100644 index 1d13d5c4..00000000 --- a/tests/helpers/script.py +++ /dev/null @@ -1,186 +0,0 @@ -import os.path - -from .lock import Lockfile, LockTimeoutError - - -######################## -# labels - -class InvalidLabelError(ValueError): - """A label is not valid.""" - - def __init__(self, label): - msg = 'label {!r} not valid'.format(label) - super(InvalidLabelError, self).__init__(msg) - self.label = label - - -class LabelNotFoundError(RuntimeError): - """A script label (e.g. "# ") was not found.""" - - def __init__(self, label): - msg = 'label {!r} not found'.format(label) - super(LabelNotFoundError, self).__init__(msg) - self.label = label - - -def check_label(label): - """Raise InvalidLabelError is the label is not valid.""" - label = str(label) - if not label: - raise InvalidLabelError(label) - - -def iter_until_label(lines, label): - """Yield (line, found) for each line until the label matches. - - A label is a line that looks like "# " (leading whitespace - is ignored). If the label is not found then LabelNotFoundError - is raised. - - "lines" should be an iterator of the lines of a script (with or - without EOL). It also works with any other iterable (e.g. a list), - but only iterators preserve position. - """ - check_label(label) - - expected = '# <{}>'.format(label) - for line in lines: - if line.strip() == expected: - yield line, True - break - yield line, False - else: - raise LabelNotFoundError(label) - - -def find_line(script, label): - """Return the line number (1-based) of the line after the label.""" - lines = iter(script.splitlines()) - # Line numbers start with 1. - for lineno, _ in enumerate(iter_until_label(lines, label), 1): - pass - return lineno + 1 # the line after - - -######################## -# wait points - -def _indent(script, line): - indent = ' ' * (len(line) - len(line.lstrip(' '))) - if not indent: - return script - return indent + (os.linesep + indent).join(script.splitlines()) - - -def insert_release(script, lockfile, label=None): - """Return (script, wait func) after adding a done script to the original. - - If a label is provided then the done script is inserted just before - the label. Otherwise it is added to the end of the script. - - The script will unblock the wait func at the label (or the end). - """ - if isinstance(lockfile, str): - lockfile = Lockfile(lockfile) - - donescript, wait = lockfile.wait_for_script() - if label is None: - script += donescript - else: - leading = [] - lines = iter(script.splitlines()) - for line, matched in iter_until_label(lines, label): - if matched: - donescript = _indent(donescript, line) - leading.extend([ - donescript, - line, - '', # Make sure the label is on its own line. - ]) - break - leading.append(line) - # TODO: Use os.linesep? - script = '\n'.join(leading) + '\n'.join(lines) - return script, wait - - -def set_release(filename, lockfile, label=None, script=None): - """Return (script, wait func) after adding a done script to the original. - - In addition to the functionality of insert_release(), this function - writes the resulting script to the given file. If no original - script is given then it is read from the file. - """ - if script is None: - if not os.path.exists(filename): - raise ValueError( - 'invalid filename {!r} (file missing)'.format(filename)) - with open(filename) as scriptfile: - script = scriptfile.read() - - script, wait = insert_release(script, lockfile, label) - - with open(filename, 'w') as scriptfile: - scriptfile.write(script) - - return script, wait - - -def insert_lock(script, lockfile, label=None, timeout=5): - """Return (done func, script) after adding a wait script to the original. - - If a label is provided then the wait script is inserted just before - the label. Otherwise it is added to the end of the script. - - The script will pause at the label (or the end) until the returned - "done" func is called or the timeout is reached. - """ - if isinstance(lockfile, str): - lockfile = Lockfile(lockfile) - - try: - done, waitscript = lockfile.wait_in_script(timeout=timeout) - except LockTimeoutError: - raise RuntimeError('lock file {!r} already exists'.format(lockfile)) - - if label is None: - script += waitscript - else: - leading = [] - lines = iter(script.splitlines()) - for line, matched in iter_until_label(lines, label): - if matched: - waitscript = _indent(waitscript, line) - leading.extend([ - waitscript, - line, - '', # Make sure the label is on its own line. - ]) - break - leading.append(line) - # TODO: Use os.linesep? - script = '\n'.join(leading) + '\n'.join(lines) - return done, script - - -def set_lock(filename, lockfile, label=None, script=None, timeout=5): - """Return (done func, script) after adding a wait script to the original. - - In addition to the functionality of insert_lock(), this function - writes the resulting script to the given file. If no original - script is given then it is read from the file. - """ - if script is None: - if not os.path.exists(filename): - raise ValueError( - 'invalid filename {!r} (file missing)'.format(filename)) - with open(filename) as scriptfile: - script = scriptfile.read() - - done, script = insert_lock(script, lockfile, label, timeout) - - with open(filename, 'w') as scriptfile: - scriptfile.write(script) - - return done, script diff --git a/pytests/helpers/session.py b/tests/helpers/session.py similarity index 100% rename from pytests/helpers/session.py rename to tests/helpers/session.py diff --git a/tests/helpers/socket.py b/tests/helpers/socket.py deleted file mode 100644 index 6e98af46..00000000 --- a/tests/helpers/socket.py +++ /dev/null @@ -1,152 +0,0 @@ -from __future__ import absolute_import - -from collections import namedtuple -import contextlib -import socket - -import ptvsd.socket as _ptvsd - - -convert_eof = _ptvsd.convert_eof - - -def resolve_hostname(): - hostname = socket.gethostname() - try: - return socket.gethostbyname(hostname) - except socket.gaierror: - addr = ('8.8.8.8', 80) - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - try: - sock.connect(addr) - return sock.getsockname()[0] - finally: - sock.close() - - -# TODO: Add timeouts to the functions. - -def create_server(address): - """Return a server socket after binding.""" - return _ptvsd.create_server(*address) - - -def create_client(): - """Return a new (unconnected) client socket.""" - return _ptvsd.create_client() - - -def connect(sock, address): - """Return a client socket after connecting. - - If address is None then it's a server, so it will wait for a - connection. Otherwise it will connect to the remote host. - """ - return _connect(sock, address) - - -def bind(address): - """Return (connect, remote addr) for the given address. - - "connect" is a function with no args that returns (client, server), - which are sockets. If the host is None then a server socket will - be created bound to localhost, and that server socket will be - returned from connect(). Otherwise a client socket is connected to - the remote address and None is returned from connect() for the - server. - """ - host, _ = address - if host is None: - sock = create_server(address) - server = sock - connect_to = None - remote = sock.getsockname() - else: - sock = create_client() - server = None - connect_to = address - remote = address - - def connect(): - client = _connect(sock, connect_to) - return client, server - return connect, remote - - -def recv_as_read(sock): - """Return a wrapper ardoung sock.read that arises EOFError when closed.""" - def read(numbytes, _recv=sock.recv): - with convert_eof(): - return _recv(numbytes) - return read - - -def send_as_write(sock): - """Return a wrapper ardoung sock.send that arises EOFError when closed.""" - def write(data, _send=sock.send): - with convert_eof(): - return _send(data) - return write - - -@contextlib.contextmanager -def timeout(sock, timeout): - """A context manager that sets a timeout on blocking socket ops.""" - orig = sock.gettimeout() - sock.settimeout(timeout) - try: - yield - finally: - sock.settimeout(orig) - - -def close(sock): - """Shutdown and close the socket.""" - _ptvsd.close_socket(sock) - - -class Connection(namedtuple('Connection', 'client server')): - """A wrapper around a client socket. - - If a server socket is provided then it will be closed when the - client is closed. - """ - - def __new__(cls, client, server=None): - self = super(Connection, cls).__new__( - cls, - client, - server, - ) - return self - - def send(self, *args, **kwargs): - return self.client.send(*args, **kwargs) - - def recv(self, *args, **kwargs): - return self.client.recv(*args, **kwargs) - - def makefile(self, *args, **kwargs): - return self.client.makefile(*args, **kwargs) - - def shutdown(self, *args, **kwargs): - if self.server is not None: - _ptvsd.shut_down(self.server, *args, **kwargs) - _ptvsd.shut_down(self.client, *args, **kwargs) - - def close(self): - if self.server is not None: - self.server.close() - self.client.close() - - -######################## -# internal functions - -def _connect(sock, address): - if address is None: - client, _ = sock.accept() - else: - sock.connect(address) - client = sock - return client diff --git a/tests/helpers/stub.py b/tests/helpers/stub.py deleted file mode 100644 index 122338fd..00000000 --- a/tests/helpers/stub.py +++ /dev/null @@ -1,25 +0,0 @@ - - -class Stub(object): - """A testing double that tracks calls.""" - - def __init__(self): - self.calls = [] - self._exceptions = [] - - def set_exceptions(self, *exceptions): - self._exceptions = list(exceptions) - - def add_call(self, name, *args, **kwargs): - self.add_call_exact(name, args, kwargs) - - def add_call_exact(self, name, args=None, kwargs=None): - self.calls.append((name, args, kwargs)) - - def maybe_raise(self): - if not self._exceptions: - return - exc = self._exceptions.pop(0) - if exc is None: - return - raise exc diff --git a/pytests/helpers/test_pattern.py b/tests/helpers/test_pattern.py similarity index 100% rename from pytests/helpers/test_pattern.py rename to tests/helpers/test_pattern.py diff --git a/pytests/helpers/test_timeline.py b/tests/helpers/test_timeline.py similarity index 100% rename from pytests/helpers/test_timeline.py rename to tests/helpers/test_timeline.py diff --git a/tests/helpers/threading.py b/tests/helpers/threading.py deleted file mode 100644 index ab8d9d54..00000000 --- a/tests/helpers/threading.py +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import absolute_import - -import sys -import threading -import time -import warnings - -from ptvsd._util import TimeoutError - - -if sys.version_info < (3,): - def acquire_with_timeout(lock, timeout): - if lock.acquire(False): - return True - for _ in range(int(timeout * 10)): - time.sleep(0.1) - if lock.acquire(False): - return True - else: - return False -else: - def acquire_with_timeout(lock, timeout): - return lock.acquire(timeout=timeout) - - -def get_locked_and_waiter(timeout=5.0): - _timeout = timeout - lock = threading.Lock() - lock.acquire() - - def wait(timeout=_timeout, reason=None, fail=False): - if timeout is None: - timeout = _timeout - if acquire_with_timeout(lock, timeout): - lock.release() - else: - msg = 'timed out (after {} seconds) waiting'.format(timeout) - if reason: - msg += ' for {}'.format(reason) - if fail: - raise TimeoutError(msg) - warnings.warn(msg, stacklevel=2) - return lock, wait diff --git a/pytests/helpers/timeline.md b/tests/helpers/timeline.md similarity index 100% rename from pytests/helpers/timeline.md rename to tests/helpers/timeline.md diff --git a/pytests/helpers/timeline.py b/tests/helpers/timeline.py similarity index 99% rename from pytests/helpers/timeline.py rename to tests/helpers/timeline.py index 7bdf5317..b7fbb024 100644 --- a/pytests/helpers/timeline.py +++ b/tests/helpers/timeline.py @@ -10,7 +10,7 @@ import threading from ptvsd.compat import queue -from pytests.helpers import colors, pattern, print, timestamp +from tests.helpers import colors, pattern, print, timestamp class Timeline(object): diff --git a/tests/helpers/vsc/__init__.py b/tests/helpers/vsc/__init__.py deleted file mode 100644 index 133bac2b..00000000 --- a/tests/helpers/vsc/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from ._fake import FakeVSC # noqa -from ._messages import VSCMessages # noqa -from ._vsc import parse_message, RawMessage, Request, Response, Event # noqa diff --git a/tests/helpers/vsc/_fake.py b/tests/helpers/vsc/_fake.py deleted file mode 100644 index 615b3d25..00000000 --- a/tests/helpers/vsc/_fake.py +++ /dev/null @@ -1,140 +0,0 @@ -from tests.helpers import protocol, socket -from ._vsc import encode_message, iter_messages, parse_message - - -PROTOCOL = protocol.MessageProtocol( - parse=parse_message, - encode=encode_message, - iter=iter_messages, -) - - -def _bind(address): - connect, remote = socket.bind(address) - - def connect(_connect=connect): - client, server = _connect() - return socket.Connection(client, server) - return connect, remote - - -class Started(protocol.MessageDaemonStarted): - - def send_request(self, msg): - self.wait_until_connected() - return self.daemon.send_request(msg) - - -class FakeVSC(protocol.MessageDaemon): - """A testing double for a VSC debugger protocol client. - - This class facilitates sending VSC debugger protocol messages over - the socket to ptvsd. It also supports tracking (and even handling) - the responses from ptvsd. - - "handler" is a function that reacts to incoming responses and events - from ptvsd. It takes a single response/event, along with a function - for sending messages (requests, events) to ptvsd. - - Example usage: - - >>> pydevd = FakePyDevd() - >>> fake = FakeVSC(lambda h, p: pydevd.start) - >>> fake.start(None, 8888) - >>> with fake.start(None, 8888): - ... fake.send_request('') - ... # wait for events... - ... - >>> fake.assert_received(testcase, [ - ... # messages - ... ]) - >>> - - See debugger_protocol/messages/README.md for more about the - protocol itself. - """ # noqa - - STARTED = Started - EXTERNAL = True - - PROTOCOL = PROTOCOL - - def __init__(self, start_adapter, handler=None): - super(FakeVSC, self).__init__( - _bind, - PROTOCOL, - handler, - ) - - def start_adapter(address, start=start_adapter): - self._adapter = start(address) - return self._adapter - self._start_adapter = start_adapter - self._adapter = None - - def start(self, address): - """Start the fake and the adapter.""" - if self._adapter is not None: - raise RuntimeError('already started') - return super(FakeVSC, self).start(address) - - def send_request(self, req): - """Send the given Request object.""" - return self.send_message(req) - - def wait_for_response(self, req, **kwargs): - reqseq = req['seq'] - command = req['command'] - - def match(msg): - #msg = parse_message(msg) - try: - actual = msg.request_seq - except AttributeError: - return False - if actual != reqseq: - return False - assert(msg.command == command) - return True - - kwargs.setdefault('handlername', - ''.format(command, reqseq)) - return self.wait_for_message(match, req, **kwargs) - - def wait_for_event(self, event, **kwargs): - def match(msg): - #msg = parse_message(msg) - try: - actual = msg.event - except AttributeError: - return False - if actual != event: - return False - return True - - kwargs.setdefault('handlername', - ''.format(event)) - return self.wait_for_message(match, req=None, **kwargs) - - # internal methods - - def _start(self, address): - host, port = address - if host is None: - # The adapter is the server so start it first. - adapter = self._start_adapter((None, port)) - return super(FakeVSC, self)._start(adapter.address) - else: - # The adapter is the client so start it last. - # TODO: For now don't use this. - raise NotImplementedError - addr, starting = super(FakeVSC, self)._start(address) - self._start_adapter(addr) - # TODO Wait for adapter to be ready? - return addr, starting - - def _close(self): - if self._adapter is not None: - self._adapter.close() - self._adapter = None - super(FakeVSC, self)._close() diff --git a/tests/helpers/vsc/_messages.py b/tests/helpers/vsc/_messages.py deleted file mode 100644 index 9cb0a172..00000000 --- a/tests/helpers/vsc/_messages.py +++ /dev/null @@ -1,66 +0,0 @@ -from tests.helpers.protocol import MessageCounters -from ._fake import FakeVSC - - -class VSCMessages(object): - - protocol = FakeVSC.PROTOCOL - - def __init__(self, - request_seq=0, # VSC requests to ptvsd - response_seq=0, # ptvsd responses/events to VSC - event_seq=None, - ): - self.counters = MessageCounters( - request_seq, - response_seq, - event_seq, - ) - - def __getattr__(self, name): - return getattr(self.counters, name) - - def new_request(self, command, seq=None, **args): - """Return a new VSC request message.""" - if seq is None: - seq = self.counters.next_request() - return { - 'type': 'request', - 'seq': seq, - 'command': command, - 'arguments': args, - } - - def new_response(self, req, seq=None, **body): - """Return a new VSC response message.""" - return self._new_response(req, None, seq, body) - - def new_failure(self, req, err, seq=None, **body): - """Return a new VSC response message.""" - return self._new_response(req, err, body=body) - - def _new_response(self, req, err=None, seq=None, body=None): - if seq is None: - seq = self.counters.next_response() - return { - 'type': 'response', - 'seq': seq, - 'request_seq': req['seq'], - 'command': req['command'], - 'success': err is None, - 'message': err or '', - 'body': body, - } - - def new_event(self, eventname, seq=None, **body): - """Return a new VSC event message.""" - if seq is None: - seq = self.counters.next_event() - if eventname == 'stopped': - body['allThreadsStopped'] = True - return { - 'type': 'event', - 'seq': seq, - 'event': eventname, - 'body': body, - } diff --git a/tests/helpers/vsc/_vsc.py b/tests/helpers/vsc/_vsc.py deleted file mode 100644 index 05456aec..00000000 --- a/tests/helpers/vsc/_vsc.py +++ /dev/null @@ -1,240 +0,0 @@ -from collections import namedtuple -import json -import sys - -from debugger_protocol.messages import wireformat -from tests.helpers.protocol import StreamFailure - -# TODO: Use more of the code from debugger_protocol. - - -if sys.version_info[0] > 2: - unicode = str - - -class ProtocolMessageError(Exception): pass # noqa -class MalformedMessageError(ProtocolMessageError): pass # noqa -class IncompleteMessageError(MalformedMessageError): pass # noqa -class UnsupportedMessageTypeError(ProtocolMessageError): pass # noqa - - -def parse_message(msg): - """Return a message object for the given "msg" data.""" - if type(msg) is str or type(msg) is unicode: - data = json.loads(msg) - elif isinstance(msg, bytes): - data = json.loads(msg.decode('utf-8')) - elif type(msg) is RawMessage: - try: - msg.data['seq'] - msg.data['type'] - except KeyError: - return msg - return parse_message(msg.data) - elif isinstance(msg, ProtocolMessage): - if msg.TYPE is not None: - return msg - try: - ProtocolMessage._look_up(msg.type) - except UnsupportedMessageTypeError: - return msg - data = msg.as_data() - else: - data = msg - - cls = look_up(data) - try: - return cls.from_data(**data) - except IncompleteMessageError: - # TODO: simply fail? - return RawMessage.from_data(**data) - - -def encode_message(msg): - """Return the line-formatted bytes for the message.""" - return wireformat.as_bytes(msg) - - -def iter_messages(stream, stop=lambda: False): - """Yield the correct message for each line-formatted one found.""" - while not stop(): - try: - #msg = wireformat.read(stream, lambda _: RawMessage) - msg = wireformat.read(stream, look_up) - if msg is None: # EOF - break - yield msg - except Exception as exc: - yield StreamFailure('recv', None, exc) - - -def look_up(data): - """Return the message type to use.""" - try: - msgtype = data['type'] - except KeyError: - # TODO: return RawMessage? - ProtocolMessage._check_data(data) - try: - return ProtocolMessage._look_up(msgtype) - except UnsupportedMessageTypeError: - # TODO: return Message? - raise - - -class RawMessage(namedtuple('RawMessage', 'data')): - """A wrapper around a line-formatted debugger protocol message.""" - - @classmethod - def from_data(cls, **data): - """Return a RawMessage for the given JSON-decoded data.""" - return cls(data) - - def __new__(cls, data): - if type(data) is cls: - return data - self = super(RawMessage, cls).__new__(cls, data) - return self - - def as_data(self): - """Return the corresponding data, ready to be JSON-encoded.""" - return self.data - - -class ProtocolMessage(object): - """The base type for VSC debug adapter protocol message.""" - - TYPE = None - - @classmethod - def from_data(cls, **data): - """Return a message for the given JSON-decoded data.""" - try: - return cls(**data) - except TypeError: - cls._check_data(data) - raise - - @classmethod - def _check_data(cls, data): - missing = set(cls._fields) - set(data) - if missing: - raise IncompleteMessageError(','.join(missing)) - - @classmethod - def _look_up(cls, msgtype): - if msgtype == 'request': - return Request - elif msgtype == 'response': - return Response - elif msgtype == 'event': - return Event - else: - raise UnsupportedMessageTypeError(msgtype) - - def __new__(cls, seq, type, **kwargs): - if cls is ProtocolMessage: - return Message(seq, type, **kwargs) - seq = int(seq) - type = str(type) if type else None - unused = {k: kwargs.pop(k) - for k in tuple(kwargs) - if k not in cls._fields} - self = super(ProtocolMessage, cls).__new__(cls, seq, type, **kwargs) - self._unused = unused - return self - - def __init__(self, *args, **kwargs): - if self.TYPE is None: - if self.type is None: - raise TypeError('missing type') - elif self.type != self.TYPE: - msg = 'wrong type (expected {!r}, go {!r}' - raise ValueError(msg.format(self.TYPE, self.type)) - - def __repr__(self): - raw = super(ProtocolMessage, self).__repr__() - if self.TYPE is None: - return raw - return ', '.join(part - for part in raw.split(', ') - if not part.startswith('type=')) - - @property - def unused(self): - return dict(self._unused) - - def as_data(self): - """Return the corresponding data, ready to be JSON-encoded.""" - data = self._asdict() - data.update(self._unused) - return data - - -class Message(ProtocolMessage, namedtuple('Message', 'seq type')): - """A generic DAP message.""" - - def __getattr__(self, name): - try: - return self._unused[name] - except KeyError: - raise AttributeError(name) - - -class Request(ProtocolMessage, - namedtuple('Request', 'seq type command arguments')): - """A DAP request message.""" - - TYPE = 'request' - - def __new__(cls, seq, type, command, arguments, **unused): - # TODO: Make "arguments" immutable? - return super(Request, cls).__new__( - cls, - seq, - type, - command=command, - arguments=arguments, - **unused - ) - - -class Response(ProtocolMessage, - namedtuple('Response', - 'seq type request_seq command success message body'), - ): - """A DAP response message.""" - - TYPE = 'response' - - def __new__(cls, seq, type, request_seq, command, success, message, body, - **unused): - # TODO: Make "body" immutable? - return super(Response, cls).__new__( - cls, - seq, - type, - request_seq=request_seq, - command=command, - success=success, - message=message, - body=body, - **unused - ) - - -class Event(ProtocolMessage, namedtuple('Event', 'seq type event body')): - """A DAP event message.""" - - TYPE = 'event' - - def __new__(cls, seq, type, event, body, **unused): - # TODO: Make "body" immutable? - return super(Event, cls).__new__( - cls, - seq, - type, - event=event, - body=body, - **unused - ) diff --git a/pytests/helpers/watchdog.py b/tests/helpers/watchdog.py similarity index 100% rename from pytests/helpers/watchdog.py rename to tests/helpers/watchdog.py diff --git a/tests/helpers/webhelper.py b/tests/helpers/webhelper.py index df5028cd..428ee72b 100644 --- a/tests/helpers/webhelper.py +++ b/tests/helpers/webhelper.py @@ -1,11 +1,18 @@ -import requests +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See LICENSE in the project root +# for license information. +import threading +import requests +import re +import socket +import time def get_web_string(path, obj): r = requests.get(path) content = r.text if obj is not None: - obj['content'] = content + obj.content = content return content @@ -14,3 +21,46 @@ def get_web_string_no_error(path, obj): return get_web_string(path, obj) except Exception: pass + + +re_link = r"(http(s|)\:\/\/[\w\.]*\:[0-9]{4,6}(\/|))" +def get_url_from_str(s): + matches = re.findall(re_link, s) + if matches and matches[0]and matches[0][0].strip(): + return matches[0][0] + return None + + +def get_web_content(link, web_result=None, timeout=1): + class WebResponse(object): + def __init__(self): + self.content = None + + def wait_for_response(self, timeout=1): + self._web_client_thread.join(timeout) + return self.content + + response = WebResponse() + response._web_client_thread = threading.Thread( + target=get_web_string_no_error, + args=(link, response), + name='test.webClient' + ) + response._web_client_thread.start() + return response + + +def wait_for_connection(port, interval=1, attempts=10): + count = 0 + while count < attempts: + count += 1 + try: + print('Waiting to connect to port: %s' % port) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', port)) + return + except socket.error: + pass + finally: + sock.close() + time.sleep(interval) diff --git a/tests/helpers/workspace.py b/tests/helpers/workspace.py deleted file mode 100644 index 16c2bb3c..00000000 --- a/tests/helpers/workspace.py +++ /dev/null @@ -1,209 +0,0 @@ -import os -import os.path -import shutil -import sys -import tempfile -from textwrap import dedent - -from .lock import Lockfile - - -# Warning: We use an "internal" stdlib function here. While the -# risk of breakage is low, it is possible... -_NAMES = tempfile._get_candidate_names() - - -def _random_name(prefix='', suffix=''): - # We do not expect to ever hit StopIteration here. - name = next(_NAMES) - return prefix + name + suffix - - -def _touch(filename): - with open(filename, 'w'): - pass - - -class FSTreeBase(object): - """File operations relative to some root directory.""" - - @classmethod - def _new_root(cls): - raise NotImplementedError - - def __init__(self, root): - if root is not None: - self._root = root - self._owned = False - - @property - def root(self): - try: - return self._root - except AttributeError: - self._root = self._new_root() - self._owned = True - return self._root - - @property - def parent(self): - parent = os.path.dirname(self.root) - return FSTreeBase(parent) - - def resolve(self, *path): - """Return the absolute path (relative to the workspace).""" - return os.path.join(self.root, *path) - - def sub(self, *path): - cls = type(self) - root = self.resolve(*path) - return cls(root) - - def env_with_py_path(self, *path): - return {'PYTHONPATH': self.root} - - -class ReadonlyFSTree(FSTreeBase): - """File operations relative to some root directory.""" - - def __init__(self, root): - assert root - super(ReadonlyFSTree, self).__init__(root) - - -class Workspace(FSTreeBase): - """File operations relative to some root directory ("workspace").""" - - PREFIX = 'workspace-' - - @classmethod - def _new_root(cls): - return tempfile.mkdtemp(prefix=cls.PREFIX) - - def __init__(self, root=None): - super(Workspace, self).__init__(root) - - def cleanup(self): - """Release and destroy the workspace.""" - if self._owned: - shutil.rmtree(self._root) - self._owned = False - self._root = None - - def random(self, *dirpath, **kwargs): - """Return a random filename resolved to the given directory.""" - dirname = self.resolve(*dirpath) - name = _random_name(**kwargs) - return os.path.join(dirname, name) - - def ensure_dir(self, *dirpath, **kwargs): - dirname = self.resolve(*dirpath) - if not os.path.exists(dirname): - os.makedirs(dirname, **kwargs) - return dirname - - def write(self, *path, **kwargs): - return self._write(path, **kwargs) - - def write_script(self, *path, **kwargs): - return self._write_script(path, **kwargs) - - def write_python_script(self, *path, **kwargs): - return self._write_script(path, executable=sys.executable, **kwargs) - - def lockfile(self, filename=None): - """Return a lockfile in the workspace.""" - filename = self._resolve_lock(filename) - return Lockfile(filename) - - # internal methods - - def _write(self, path, content='', fixup=True): - if fixup: - content = dedent(content) - filename = self.resolve(*path) - with open(filename, 'w') as outfile: - outfile.write(content) - return filename - - def _write_script(self, path, executable, mode='0755', content='', - fixup=True): - if isinstance(mode, str): - mode = int(mode, base=8) - if fixup: - content = dedent(content) - content = '#!/usr/bin/env {}\n'.format(executable) + content - filename = self._write(path, content, fixup=False) - os.chmod(filename, mode) - return filename - - def _get_locksdir(self): - try: - return self._locksdir - except AttributeError: - self._locksdir = '.locks' - self.ensure_dir(self._locksdir) - return self._locksdir - - def _resolve_lock(self, name=None): - if not name: - name = _random_name(suffix='.lock') - return self.resolve(self._get_locksdir(), name) - - -class PathEntry(Workspace): - - def __init__(self, root=None): - super(PathEntry, self).__init__(root) - self._syspath = None - - def cleanup(self): - self.uninstall() - super(PathEntry, self).cleanup() - - def install(self): - if self._syspath is not None: - return - if sys.path[0] in ('', '.'): - self._syspath = 1 - else: - self._syspath = 0 - sys.path.insert(self._syspath, self.root) - - def uninstall(self): - if self._syspath is None: - return - del sys.path[self._syspath] - self._syspath = None - - def resolve_module(self, name): - parts = (name + '.py').split('.') - return self.resolve(*parts) - - def write_module(self, name, content=''): - parent, sep, name = name.rpartition('.') - filename = name + '.py' - if sep: - dirname = self._ensure_package(parent) - filename = os.path.join(dirname, filename) - return self.write(filename, content=content) - - # internal methods - - def _ensure_package(self, name, root=None): - parent, sep, name = name.rpartition('.') - if sep: - dirname = self._ensure_package(parent, root) - else: - if root is None: - root = self.root - dirname = root - dirname = os.path.join(dirname, name) - - initpy = os.path.join(dirname, '__init__.py') - if not os.path.exists(initpy): - os.mkdirs(dirname) - with open(initpy, 'w'): - pass - - return dirname diff --git a/tests/highlevel/__init__.py b/tests/highlevel/__init__.py deleted file mode 100644 index 5e72d3bb..00000000 --- a/tests/highlevel/__init__.py +++ /dev/null @@ -1,986 +0,0 @@ -from collections import namedtuple -import contextlib -import inspect -import platform -import time -import warnings - -from _pydevd_bundle.pydevd_comm import ( - CMD_VERSION, - CMD_LIST_THREADS, - CMD_THREAD_SUSPEND, - CMD_REDIRECT_OUTPUT, - CMD_RETURN, - CMD_RUN, - CMD_STEP_CAUGHT_EXCEPTION, - CMD_SEND_CURR_EXCEPTION_TRACE, - CMD_THREAD_CREATE, - CMD_GET_THREAD_STACK, - CMD_GET_EXCEPTION_DETAILS, - CMD_PYDEVD_JSON_CONFIG, - CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION, -) - -from ptvsd._util import new_hidden_thread -from tests.helpers.pydevd import FakePyDevd, PyDevdMessages -from tests.helpers.vsc import FakeVSC, VSCMessages - -OS_ID = 'WINDOWS' if platform.system() == 'Windows' else 'UNIX' - - -@contextlib.contextmanager -def noop_cm(*args, **kwargs): - yield - - -def _get_caller(): - caller = inspect.currentframe() - filename = caller.f_code.co_filename - while filename == __file__ or filename == contextlib.__file__: - caller = caller.f_back - filename = caller.f_code.co_filename - return caller - - -class Thread(namedtuple('Thread', 'id name')): - """Information about a thread.""" - - PREFIX = 'Thread-' - - @classmethod - def from_raw(cls, raw): - """Return a Thread corresponding to the given value.""" - if isinstance(raw, cls): - return raw - elif isinstance(raw, str): - return cls(None, raw) - elif isinstance(raw, int): - return cls(raw) - else: - return cls(*raw) - - def __new__(cls, id, name=None): - id = int(id) if id or id == 0 else None - name = str(name) if name else cls.PREFIX + str(id) - self = super(Thread, cls).__new__(cls, id, name) - return self - - def __init__(self, *args, **kwargs): - if self.id is None: - raise TypeError('missing id') - - -class ThreadAlreadyExistsError(RuntimeError): - pass - - -class Threads(object): - - MAIN = 'MainThread' - - def __init__(self): - self._history = [] - self._alive = {} - self._names = set() - - self._main = self.add(self.MAIN) - - def __repr__(self): - return '<{}(alive={!r}, numkilled={!r})>'.format( - type(self).__name__, - self.alive, - len(self._history) - len(self._alive), - ) - - @property - def history(self): - return list(self._history) - - @property - def alive(self): - return set(self._alive.values()) - - @property - def main(self): - return self._main - - def add(self, name=None): - tid = len(self._history) + 1 - if name and name in self._names: - raise ThreadAlreadyExistsError(name) - thread = Thread(tid, name) - self._history.append(thread) - self._alive[tid] = thread - self._names.add(name) - return thread - - def remove(self, thread): - if not hasattr(thread, 'id'): - thread = self._alive[thread] - if thread.name == self.MAIN: - raise RuntimeError('cannot remove main thread') - self._remove(thread) - return thread - - def _remove(self, thread): - del self._alive[thread.id] - self._names.remove(thread.name) - - def clear(self, keep=None): - keep = {self.MAIN} | set(keep or ()) - for t in self.alive: - if t.name not in keep: - self._remove(t) - - -class PyDevdLifecycle(object): - - def __init__(self, fix): - self._fix = fix - - @contextlib.contextmanager - def _wait_for_initialized(self): - with self._fix.wait_for_command(CMD_REDIRECT_OUTPUT): - with self._fix.wait_for_command(CMD_PYDEVD_JSON_CONFIG): - with self._fix.wait_for_command(CMD_RUN): - yield - - def _initialize(self): - version = self._fix.fake.VERSION - self._fix.set_response(CMD_VERSION, version) - - def notify_main_thread(self): - self._fix.notify_main_thread() - - -class VSCLifecycle(object): - - PORT = 8888 - - MIN_INITIALIZE_ARGS = { - 'adapterID': '', - } - - def __init__(self, fix, pydevd=None, hidden=None): - self._fix = fix - self._pydevd = pydevd - self._hidden = hidden or fix.hidden - self.requests = None - - @contextlib.contextmanager - def daemon_running(self, port=None, hide=False, disconnect=True): - with self._fix.hidden() if hide else noop_cm(): - daemon = self._start_daemon(port) - try: - yield - finally: - with self._fix.hidden() if hide else noop_cm(): - self._stop_daemon(daemon, disconnect=disconnect) - - @contextlib.contextmanager - def launched(self, port=None, hide=False, disconnect=True, **kwargs): - with self.daemon_running(port, hide=hide, disconnect=disconnect): - self.launch(**kwargs) - yield - - @contextlib.contextmanager - def attached(self, port=None, hide=False, disconnect=True, **kwargs): - with self.daemon_running(port, hide=hide, disconnect=disconnect): - self.attach(**kwargs) - yield - - def launch(self, **kwargs): - """Initialize the debugger protocol and then launch.""" - with self._hidden(): - self._handshake('launch', **kwargs) - - def attach(self, **kwargs): - """Initialize the debugger protocol and then attach.""" - with self._hidden(): - self._handshake('attach', **kwargs) - - def disconnect(self, exitcode=0, **reqargs): - self._fix.daemon.exitcode = exitcode - self._send_request('disconnect', reqargs) - # TODO: wait for an exit event? - # TODO: call self._fix.vsc.close()? - - # internal methods - - def _send_request(self, command, args=None, handle_response=None): - if self.requests is None: - return self._fix.send_request(command, args, handle_response) - - txn = [None, None] - self.requests.append(txn) - - def handler(msg, send, _handle_resp=handle_response): - txn[1] = msg - if _handle_resp is not None: - _handle_resp(msg, send) - - req = self._fix.send_request(command, args, handler) - txn[0] = req - return req - - def _start_daemon(self, port): - if port is None: - port = self.PORT - addr = (None, port) - with self._hidden(): - # Anything that gets sent in VSCodeMessageProcessor.__init__() - # must be waited for here. - # TODO: Is this necessary any more since we added the "readylock"? - with self._fix.wait_for_event('output'): - daemon = self._fix.fake.start(addr) - daemon.wait_until_connected() - return daemon - - def _stop_daemon(self, daemon, disconnect=True, timeout=10.0): - # We must close ptvsd directly (rather than closing the external - # socket (i.e. "daemon"). This is because cloing ptvsd blocks, - # keeping us from sending the disconnect request we need to send - # at the end. - t = new_hidden_thread( - target=self._fix.close_ptvsd, - name='test.lifecycle', - ) - #with self._fix.wait_for_events(['exited', 'terminated']): - if True: - # The thread runs close_ptvsd(), which sends the two - # events and then waits for a "disconnect" request. We send - # that after we receive the events. - t.start() - if disconnect: - self.disconnect() - t.join(timeout) - if t.isAlive(): - raise Exception( - '_stop_daemon timed out after {} secs'.format(timeout)) - daemon.close() - - def _handshake(self, command, threadnames=None, config=None, requests=None, - default_threads=True, process=True, reset=True, - **kwargs): - initargs = dict( - kwargs.pop('initargs', None) or {}, - disconnect=kwargs.pop('disconnect', True), - ) - - with self._fix.wait_for_event('initialized'): - self._initialize(**initargs) - self._send_request(command, **kwargs) - - if threadnames: - self._fix.set_threads(*threadnames, - **dict(default_threads=default_threads)) - - self._handle_config(**config or {}) - with self._wait_for_debugger_init(): - self._send_request('configurationDone') - - if process: - with self._fix.wait_for_event('process'): - with self._fix.wait_for_event('thread'): - if self._pydevd: - self._pydevd.notify_main_thread() - - if reset: - self._fix.reset() - else: - self._fix.assert_no_failures() - - @contextlib.contextmanager - def _wait_for_debugger_init(self): - if self._pydevd: - with self._pydevd._wait_for_initialized(): - yield - else: - yield - - def _initialize(self, **reqargs): - """ - See https://code.visualstudio.com/docs/extensionAPI/api-debugging#_the-vs-code-debug-protocol-in-a-nutshell - """ # noqa - - def handle_response(resp, _): - self._capabilities = resp.body - - if self._pydevd: - self._pydevd._initialize() - self._send_request( - 'initialize', - dict(self.MIN_INITIALIZE_ARGS, **reqargs), - handle_response, - ) - - def _handle_config(self, breakpoints=None, excbreakpoints=None): - for req in breakpoints or (): - self._send_request( - 'setBreakpoints', - self._parse_breakpoints(req), - ) - for req in excbreakpoints or (): - self._send_request( - 'setExceptionBreakpoints', - self._parse_exception_breakpoints(req), - ) - - def _parse_breakpoints(self, req): - # setBreakpoints request: - # source : - # --- - # breakpoints : [] - # lines : [int] - # sourceModified : bool - # : - # --- - # name : str - # path : str - # sourceReference : num - # presentationHint : enum - # origin : str - # sources : [] - # adapterData : * - # checksums : [] - # : - # algorithm : enum - # checksum : str - # --- - # : - # line : int - # --- - # column : int - # condition : str - # hitCondition : str - # logMessage : str - # TODO: validate? - return req - - def _parse_exception_breakpoints(self, req): - # setExceptionBreakpoints request: - # filters : [str] - # --- - # exceptionOptions : [] - # : - # breakMode : enum - # --- - # path : [] - # : - # names : [str] - # --- - # negate : bool - # TODO: validate? - return req - - -class FixtureBase(object): - """Base class for protocol daemon test fixtures.""" - - def __init__(self, new_fake, new_msgs): - if not callable(new_fake): - raise ValueError('bad new_fake {!r}'.format(new_fake)) - - self._new_fake = new_fake - self.msgs = new_msgs() - self._hidden = False - - @property - def fake(self): - try: - return self._fake - except AttributeError: - self._fake = self.new_fake() - # Uncomment the following 2 lines to see all messages. - #self._fake.PRINT_SENT_MESSAGES = True - #self._fake.PRINT_RECEIVED_MESSAGES = True - return self._fake - - @property - def ishidden(self): - return self._hidden - - @contextlib.contextmanager - def hidden(self): - received = self.fake.received - orig = self._hidden - self._hidden = True - try: - yield - finally: - self._hidden = orig - self.fake.reset(*received) - - def set_fake(self, fake): - if hasattr(self, '_fake'): - raise AttributeError('fake already set') - self._fake = fake - - def new_fake(self, handler=None, **kwargs): - """Return a new fake that may be used in tests.""" - return self._new_fake(handler=handler, **kwargs) - - def assert_no_failures(self): - assert self.fake.failures == [], self.fake.failures - - def reset(self, **kwargs): - self.assert_no_failures() - self.fake.reset(**kwargs) - - -class PyDevdFixture(FixtureBase): - """A test fixture for the PyDevd protocol.""" - - FAKE = FakePyDevd - MSGS = PyDevdMessages - - def __init__(self, new_fake=None): - if new_fake is None: - new_fake = self.FAKE - super(PyDevdFixture, self).__init__(new_fake, self.MSGS) - self._threads = Threads() - - @property - def threads(self): - return self._threads - - def notify_main_thread(self): - self.send_event( - CMD_THREAD_CREATE, - self.msgs.format_threads(self._threads.main), - ) - - @contextlib.contextmanager - def expect_command(self, cmdid): - yield - if self._hidden: - self.msgs.next_request() - - @contextlib.contextmanager - def wait_for_command(self, cmdid, *args, **kwargs): - with self.fake.wait_for_command(cmdid, *args, **kwargs): - yield - if self._hidden: - self.msgs.next_request() - - def set_response(self, cmdid, payload, **kwargs): - self.fake.add_pending_response(cmdid, payload, **kwargs) - if self._hidden: - self.msgs.next_request() - - def send_event(self, cmdid, payload): - event = self.msgs.new_event(cmdid, payload) - self.fake.send_event(event) - - def set_threads_response(self): - text = self.msgs.format_threads(*self._threads.alive) - self.set_response(CMD_RETURN, text, reqid=CMD_LIST_THREADS) - - def send_suspend_event(self, thread, reason, *stack): - thread = Thread.from_raw(thread) - self._suspend(thread, reason, stack) - - def send_pause_event(self, thread, *stack): - thread = Thread.from_raw(thread) - reason = CMD_THREAD_SUSPEND - self._suspend(thread, reason, stack) - - def _suspend(self, thread, reason, stack): - self.send_event( - CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION, - self.msgs.format_frames2(thread.id, reason, *stack) - ) - self.send_event( - CMD_THREAD_SUSPEND, - self.msgs.format_frames(thread.id, reason, *stack), - ) - - def send_caught_exception_events(self, thread, exc, *stack): - thread = Thread.from_raw(thread) - reason = CMD_STEP_CAUGHT_EXCEPTION - self._exception(thread, exc, reason, stack) - - def _exception(self, thread, exc, reason, stack): - self.send_event( - CMD_SEND_CURR_EXCEPTION_TRACE, - self.msgs.format_exception(thread.id, exc, *stack), - ) - self.send_suspend_event(thread, reason, *stack) - #self.set_exception_var_response(exc) - - def set_exception_var_response(self, threadid, exc, *frames): - self.set_response( - CMD_GET_EXCEPTION_DETAILS, - self.msgs.format_exception_details( - threadid, exc, *frames - ), - ) - - -class VSCFixture(FixtureBase): - """A test fixture for the DAP.""" - - FAKE = FakeVSC - MSGS = VSCMessages - LIFECYCLE = VSCLifecycle - START_ADAPTER = None - - def __init__(self, new_fake=None, start_adapter=None): - if new_fake is None: - new_fake = self.FAKE - if start_adapter is None: - start_adapter = self.START_ADAPTER - elif not callable(start_adapter): - raise ValueError('bad start_adapter {!r}'.format(start_adapter)) - - def new_fake(start_adapter=start_adapter, handler=None, - _new_fake=new_fake): - return _new_fake(start_adapter, handler=handler) - - super(VSCFixture, self).__init__(new_fake, self.MSGS) - - @property - def vsc(self): - return self.fake - - @property - def vsc_msgs(self): - return self.msgs - - @property - def lifecycle(self): - try: - return self._lifecycle - except AttributeError: - self._lifecycle = self.LIFECYCLE(self) - return self._lifecycle - - @property - def daemon(self): - # TODO: This is a horrendous use of internal details! - return self.fake._adapter.daemon.binder.ptvsd - - @property - def _proc(self): - # This is used below in close_ptvsd(). - try: - return self.daemon.proc - except AttributeError: - # TODO: Fall back to self.daemon.session._msgprocessor? - return None - - def send_request(self, cmd, args=None, handle_response=None, timeout=1): - kwargs = dict(args or {}, handler=handle_response) - with self._wait_for_response(cmd, timeout=timeout, **kwargs) as req: - self.fake.send_request(req) - return req - - @contextlib.contextmanager - def _wait_for_response(self, command, *args, **kwargs): - handle = kwargs.pop('handler', None) - timeout = kwargs.pop('timeout', 1) - req = self.msgs.new_request(command, *args, **kwargs) - with self.fake.wait_for_response(req, handler=handle, timeout=timeout): - yield req - if self._hidden: - self.msgs.next_response() - - @contextlib.contextmanager - def wait_for_event(self, event, *args, **kwargs): - if 'caller' not in kwargs: - caller = _get_caller() - kwargs['caller'] = (caller.f_code.co_filename, caller.f_lineno) - with self.fake.wait_for_event(event, *args, **kwargs): - yield - if self._hidden: - self.msgs.next_event() - - @contextlib.contextmanager - def wait_for_events(self, events): - if not events: - yield - return - with self.wait_for_events(events[1:]): - with self.wait_for_event(events[0]): - yield - - def get_threads(self, name='MainThread'): - threads = {} - - def handle_response(msg, _): - for t in msg.body['threads']: - threads[t['id']] = t['name'] - if t['name'] == name: - threads[None] = t['id'] - - self.send_request('threads', handle_response=handle_response) - return threads, threads.pop(None) - - def close_ptvsd(self, exitcode=None): - # TODO: Use the session instead. - if self._proc is None: - warnings.warn('"proc" not bound') - else: - self._proc.close() - self.daemon.exitcode = exitcode - self.daemon.close() - - -class HighlevelFixture(object): - - DAEMON = FakeVSC - DEBUGGER = FakePyDevd - - DEFAULT_THREADS = [ - 'ptvsd.Server', - 'pydevd.thread1', - 'pydevd.thread2', - ] - - def __init__(self, vsc=None, pydevd=None, mainthread=True): - if vsc is None: - self._new_vsc = self.DAEMON - vsc = VSCFixture(new_fake=self._new_fake_vsc) - elif callable(vsc): - self._new_vsc = vsc - vsc = VSCFixture(new_fake=self._new_fake_vsc) - else: - self._new_vsc = None - self._vsc = vsc - - if pydevd is None: - pydevd = PyDevdFixture(self.DEBUGGER) - elif callable(pydevd): - pydevd = PyDevdFixture(pydevd) - self._pydevd = pydevd - - def highlevel_lifecycle(fix, _cls=vsc.LIFECYCLE): - pydevd = PyDevdLifecycle(self._pydevd) - return _cls(fix, pydevd, self.hidden) - - vsc.LIFECYCLE = highlevel_lifecycle - - self._default_threads = None - self._known_threads = set() - if mainthread: - self._known_threads.add(self._pydevd.threads.main) - - def _new_fake_vsc(self, start_adapter=None, handler=None): - if start_adapter is None: - try: - self._default_fake_vsc - except AttributeError: - pass - else: - raise RuntimeError('default fake VSC already created') - start_adapter = self.debugger.start - return self._new_vsc(start_adapter, handler) - - @property - def vsc(self): - return self._vsc.fake - - @property - def vsc_msgs(self): - return self._vsc.msgs - - @property - def debugger(self): - return self._pydevd.fake - - @property - def debugger_msgs(self): - return self._pydevd.msgs - - @property - def lifecycle(self): - return self._vsc.lifecycle - - @property - def threads(self): - return self._pydevd.threads - - @property - def ishidden(self): - return self._vsc.ishidden and self._pydevd.ishidden - - @property - def daemon(self): - return self._vsc.daemon - - @contextlib.contextmanager - def hidden(self): - with self._vsc.hidden(): - with self._pydevd.hidden(): - yield - - def new_fake(self, debugger=None, handler=None): - """Return a new fake VSC that may be used in tests.""" - if debugger is None: - debugger = self._pydevd.new_fake() - vsc = self._vsc.new_fake(debugger.start, handler) - return vsc, debugger - - def assert_no_failures(self): - self._vsc.assert_no_failures() - self._pydevd.assert_no_failures() - - def reset(self, **kwargs): - self._vsc.reset(**kwargs) - self._debugger.reset(**kwargs) - - # wrappers - - def set_default_threads(self): - if self._default_threads is not None: - return - self._default_threads = {} - for name in self.DEFAULT_THREADS: - thread = self._pydevd.threads.add(name) - self._default_threads[name] = thread - - def send_request(self, command, args=None, handle_response=None, **kwargs): - return self._vsc.send_request(command, args, handle_response, - **kwargs) - - @contextlib.contextmanager - def wait_for_event(self, event, *args, **kwargs): - with self._vsc.wait_for_event(event, *args, **kwargs): - yield - - @contextlib.contextmanager - def wait_for_events(self, events): - with self._vsc.wait_for_events(events): - yield - - @contextlib.contextmanager - def expect_debugger_command(self, cmdid): - with self._pydevd.expect_command(cmdid): - yield - - def set_debugger_response(self, cmdid, payload, **kwargs): - self._pydevd.set_response(cmdid, payload, **kwargs) - - def send_debugger_event(self, cmdid, payload): - self._pydevd.send_event(cmdid, payload) - - def close_ptvsd(self, **kwargs): - self._vsc.close_ptvsd(**kwargs) - - # combinations - - def send_event(self, cmdid, text, event=None, handler=None): - if event is not None: - with self.wait_for_event(event, handler=handler): - self.send_debugger_event(cmdid, text) - else: - self.send_debugger_event(cmdid, text) - return None - - def set_threads(self, _threadname, *threadnames, **kwargs): - threadnames = (_threadname,) + threadnames - return self._set_threads(threadnames, **kwargs) - - def set_thread(self, threadname): - threadnames = (threadname,) - return self._set_threads(threadnames)[0] - - def _set_threads(self, threadnames, default_threads=True): - # Update the list of "alive" threads. - self._pydevd.threads.clear(keep=self.DEFAULT_THREADS) - if default_threads: - self.set_default_threads() - request = {} - threads = [] - for i, name in enumerate(threadnames): - thread = self._pydevd.threads.add(name) - threads.append(thread) - request[thread.name] = i - ignored = ('ptvsd.', 'pydevd.') - newthreads = [t - for t in self._pydevd.threads.alive - if not t.name.startswith(ignored) and - t not in self._known_threads] - - # Send and handle messages. - self._pydevd.set_threads_response() - with self.wait_for_events(['thread' for _ in newthreads]): - self.send_request('threads') - self._known_threads.update(newthreads) - - # Extract thread info from the response. - for msg in reversed(self.vsc.received): - if msg.type == 'response': - if msg.command == 'threads': - break - else: - assert False, 'we waited for the response in send_request()' - response = [(None, t) for t in threads] - for tinfo in msg.body['threads']: - try: - i = request.pop(tinfo['name']) - except KeyError: - continue - response[i] = (tinfo['id'], threads[i]) - return response - - def suspend(self, thread, reason, *stack): - with self.wait_for_event('stopped'): - if isinstance(reason, Exception): - exc = reason - self._pydevd.set_exception_var_response(thread.id, exc, *stack) - self._pydevd.send_caught_exception_events(thread, exc, *stack) - else: - self._pydevd.send_suspend_event(thread, reason, *stack) - - def pause(self, threadname, *stack): - tid, thread = self.set_thread(threadname) - self._pydevd.send_pause_event(thread, *stack) - if self._vsc._hidden: - self._vsc.msgs.next_event() - payload = self.debugger_msgs.format_frames(thread.id, 'pause', *stack) - self.set_debugger_response(CMD_GET_THREAD_STACK, payload) - self.send_request('stackTrace', {'threadId': tid}) - self.send_request('scopes', {'frameId': 1}) - return tid, thread - - def error(self, threadname, exc, frame): - tid, thread = self.set_thread(threadname) - self.suspend(thread, exc, frame) - return tid, thread - - -class VSCTest(object): - """The base mixin class for high-level VSC-only ptvsd tests.""" - - FIXTURE = VSCFixture - - _ready = False - _fix = None # overridden in setUp() - - @classmethod - def _new_daemon(cls, *args, **kwargs): - return cls.FIXTURE.FAKE(*args, **kwargs) - - def setUp(self): - super(VSCTest, self).setUp() - self._ready = True - - self.maxDiff = None - - def __getattr__(self, name): - if not self._ready: - raise AttributeError - return getattr(self.fix, name) - - @property - def fix(self): - if self._fix is None: - - def new_daemon(*args, **kwargs): - vsc = self._new_daemon(*args, **kwargs) - self.addCleanup(vsc.close) - return vsc - - try: - self._fix = self._new_fixture(new_daemon) - except AttributeError: - raise Exception - return self._fix - - @property - def new_response(self): - return self.fix.vsc_msgs.new_response - - @property - def new_failure(self): - return self.fix.vsc_msgs.new_failure - - @property - def new_event(self): - return self.fix.vsc_msgs.new_event - - def _new_fixture(self, new_daemon): - return self.FIXTURE(new_daemon) - - def assert_vsc_received(self, received, expected): - from tests.helpers.message import assert_messages_equal - - received = list(self.vsc.protocol.parse_each(received)) - expected = list(self.vsc.protocol.parse_each(expected)) - assert_messages_equal(received, expected) - - def assert_vsc_failure(self, received, expected, req): - received = list(self.vsc.protocol.parse_each(received)) - expected = list(self.vsc.protocol.parse_each(expected)) - self.assertEqual(received[:-1], expected) - - failure = received[-1] if len(received) > 0 else [] - if failure: - expected = self.vsc.protocol.parse( - self.fix.vsc_msgs.new_failure(req, failure.message)) - self.assertEqual(failure, expected) - - def assert_received(self, daemon, expected): - """Ensure that the received messages match the expected ones.""" - received = list(daemon.protocol.parse_each(daemon.received)) - expected = list(daemon.protocol.parse_each(expected)) - self.assertEqual(received, expected) - - def assert_contains(self, received, expected, parser='vsc'): - parser = self.vsc.protocol if parser == 'vsc' else parser - from tests.helpers.message import assert_contains_messages - received = list(parser.parse_each(received)) - expected = list(parser.parse_each(expected)) - assert_contains_messages(received, expected) - - def assert_received_unordered_payload(self, daemon, expected): - """Ensure that the received messages match the expected ones - regardless of payload order.""" - received = sorted(m.payload for m in - daemon.protocol.parse_each(daemon.received)) - expected = sorted(m.payload for m in - daemon.protocol.parse_each(expected)) - self.assertEqual(received, expected) - - -class HighlevelTest(VSCTest): - """The base mixin class for high-level ptvsd tests.""" - - FIXTURE = HighlevelFixture - - @classmethod - def _new_daemon(cls, *args, **kwargs): - return cls.FIXTURE.DAEMON(*args, **kwargs) - - @property - def pydevd(self): - return self.debugger - - def new_fake(self, debugger=None, handler=None): - """Return a new fake VSC that may be used in tests.""" - vsc, debugger = self.fix.new_fake(debugger, handler) - return vsc, debugger - - def wait_for_pydevd(self, *msgs, **kwargs): - timeout = kwargs.pop('timeout', 10.0) - assert not kwargs - steps = int(timeout * 100) + 1 - for _ in range(steps): - # TODO: Watch for the specific messages. - if len(self.pydevd.received) >= len(msgs): - break - time.sleep(0.01) - else: - if len(self.pydevd.received) < len(msgs): - raise RuntimeError('timed out') - - -class RunningTest(HighlevelTest): - """The mixin class for high-level tests for post-start operations.""" - - def launched(self, port, **kwargs): - return self.lifecycle.launched(port=port, **kwargs) - - def attached(self, port, **kwargs): - return self.lifecycle.attached(port=port, **kwargs) diff --git a/tests/highlevel/test_lifecycle.py b/tests/highlevel/test_lifecycle.py deleted file mode 100644 index a50b7028..00000000 --- a/tests/highlevel/test_lifecycle.py +++ /dev/null @@ -1,203 +0,0 @@ -import json -import os -import ptvsd -import sys -import unittest - -from _pydevd_bundle.pydevd_comm import ( - CMD_REDIRECT_OUTPUT, - CMD_RUN, - CMD_VERSION, - CMD_PYDEVD_JSON_CONFIG, -) - -from . import ( - OS_ID, - HighlevelTest, - HighlevelFixture, -) - - -from ptvsd.wrapper import INITIALIZE_RESPONSE - -# TODO: Make sure we are handling the following properly: -# * initialize args -# * capabilities (sent in a response) -# * setting breakpoints during config -# * sending an "exit" event. - - -def _get_project_dirs(): - vendored_pydevd = os.path.sep + \ - os.path.join('ptvsd', '_vendored', 'pydevd') - ptvsd_path = os.path.sep + 'ptvsd' - site_path = os.path.sep + 'site-packages' - - project_dirs = [] - for path in sys.path + [os.getcwd()]: - is_stdlib = False - norm_path = os.path.normcase(path) - if path.endswith((ptvsd_path, vendored_pydevd, site_path)): - is_stdlib = True - else: - for prefix in ptvsd.wrapper.STDLIB_PATH_PREFIXES: - if norm_path.startswith(prefix): - is_stdlib = True - break - - if not is_stdlib and len(path) > 0: - project_dirs.append(path) - - return '\t'.join(project_dirs) - - -class LifecycleTests(HighlevelTest, unittest.TestCase): - """ - See https://code.visualstudio.com/docs/extensionAPI/api-debugging#_the-vs-code-debug-protocol-in-a-nutshell - """ # noqa - - class FIXTURE(HighlevelFixture): - lifecycle = None # Make sure we don't cheat. - - def attach(self, expected_os_id, attach_args): - version = self.debugger.VERSION - self.fix.debugger.binder.singlesession = False - addr = (None, 8888) - daemon = self.vsc.start(addr) - with self.vsc.wait_for_event('output'): - daemon.wait_until_connected() - try: - with self.vsc.wait_for_event('initialized'): - # initialize - self.set_debugger_response(CMD_VERSION, version) - req_initialize = self.send_request('initialize', { - 'adapterID': 'spam', - }) - - # attach - req_attach = self.send_request('attach', attach_args) - - # configuration - with self._fix.wait_for_events(['process']): - req_config = self.send_request('configurationDone') - - # Normal ops would go here. - - # end - #req_disconnect = self.send_request('disconnect') - finally: - received = self.vsc.received - with self._fix.wait_for_events(['exited', 'terminated']): - self.fix.close_ptvsd() - daemon.close() - #self.fix.close_ptvsd() - - self.assert_vsc_received(received, [ - self.new_event( - 'output', - category='telemetry', - output='ptvsd', - data={'version': ptvsd.__version__}), - self.new_response(req_initialize, **INITIALIZE_RESPONSE), - self.new_event('initialized'), - self.new_response(req_attach), - self.new_response(req_config), - self.new_event('process', **dict( - name=sys.argv[0], - systemProcessId=os.getpid(), - isLocalProcess=True, - startMethod='attach', - )), - ]) - self.assert_received(self.debugger, [ - self.debugger_msgs.new_request(CMD_VERSION, - *['1.1', expected_os_id, 'ID']), - self.debugger_msgs.new_request(CMD_REDIRECT_OUTPUT), - self.debugger_msgs.new_request(CMD_PYDEVD_JSON_CONFIG, json.dumps(dict( - skip_suspend_on_breakpoint_exception=('BaseException',), - skip_print_breakpoint_exception=('NameError',), - multi_threads_single_notification=True, - ))), - self.debugger_msgs.new_request(CMD_RUN), - ]) - - def test_attach(self): - self.attach(expected_os_id=OS_ID, attach_args={}) - - @unittest.skip('not implemented') - def test_attach_exit_during_session(self): - # TODO: Ensure we see the "terminated" and "exited" events. - raise NotImplementedError - - def test_attach_from_unix_os_vsc(self): - attach_args = {'debugOptions': ['UnixClient']} - self.attach(expected_os_id='UNIX', attach_args=attach_args) - - def test_attach_from_unix_os(self): - attach_args = {'options': 'CLIENT_OS_TYPE=UNIX'} - self.attach(expected_os_id='UNIX', attach_args=attach_args) - - def test_attach_from_win_os_vsc(self): - attach_args = {'debugOptions': ['WindowsClient']} - self.attach(expected_os_id='WINDOWS', attach_args=attach_args) - - def test_attach_from_windows_os(self): - attach_args = {'options': 'CLIENT_OS_TYPE=WINDOWS'} - self.attach(expected_os_id='WINDOWS', attach_args=attach_args) - - def test_launch(self): - version = self.debugger.VERSION - addr = (None, 8888) - with self.vsc.start(addr): - with self.vsc.wait_for_event('initialized'): - # initialize - self.set_debugger_response(CMD_VERSION, version) - req_initialize = self.send_request('initialize', { - 'adapterID': 'spam', - }) - - # launch - req_launch = self.send_request('launch') - - # configuration - req_config = self.send_request('configurationDone') - self.wait_for_pydevd('version', 'redirect-output', - 'run', 'suspend_on_breakpoint_exception') - - # Normal ops would go here. - - # end - #with self.fix.wait_for_events(['exited', 'terminated']): - req_disconnect = self.send_request('disconnect') - - self.assert_received(self.vsc, [ - self.new_event( - 'output', - category='telemetry', - output='ptvsd', - data={'version': ptvsd.__version__}), - self.new_response(req_initialize, **INITIALIZE_RESPONSE), - self.new_event('initialized'), - self.new_response(req_launch), - self.new_response(req_config), - #self.new_event('process', **dict( - # name=sys.argv[0], - # systemProcessId=os.getpid(), - # isLocalProcess=True, - # startMethod='launch', - #)), - #self.new_event('exited', exitCode=0), - #self.new_event('terminated'), - self.new_response(req_disconnect), - ]) - self.assert_received(self.debugger, [ - self.debugger_msgs.new_request(CMD_VERSION, - *['1.1', OS_ID, 'ID']), - self.debugger_msgs.new_request(CMD_REDIRECT_OUTPUT), - self.debugger_msgs.new_request(CMD_PYDEVD_JSON_CONFIG, json.dumps(dict( - skip_suspend_on_breakpoint_exception=('BaseException',), - skip_print_breakpoint_exception=('NameError',), - multi_threads_single_notification=True, - ))), - self.debugger_msgs.new_request(CMD_RUN), - ]) diff --git a/tests/highlevel/test_messages.py b/tests/highlevel/test_messages.py deleted file mode 100644 index 467c5b1d..00000000 --- a/tests/highlevel/test_messages.py +++ /dev/null @@ -1,2852 +0,0 @@ -import os -import platform -import sys -import unittest -from textwrap import dedent - -from _pydevd_bundle.pydevd_comm import ( - CMD_ADD_EXCEPTION_BREAK, - CMD_CHANGE_VARIABLE, - CMD_EVALUATE_EXPRESSION, - CMD_EXEC_EXPRESSION, - CMD_EXIT, - CMD_GET_BREAKPOINT_EXCEPTION, - CMD_GET_FRAME, - CMD_GET_VARIABLE, - CMD_LIST_THREADS, - CMD_REMOVE_BREAK, - CMD_REMOVE_EXCEPTION_BREAK, - CMD_RETURN, - CMD_SET_BREAK, - CMD_SHOW_CONSOLE, - CMD_STEP_CAUGHT_EXCEPTION, - CMD_STEP_INTO, - CMD_STEP_OVER, - CMD_STEP_RETURN, - CMD_THREAD_CREATE, - CMD_THREAD_KILL, - CMD_THREAD_RUN, - CMD_THREAD_SUSPEND, - CMD_VERSION, - CMD_WRITE_TO_CONSOLE, - CMD_STEP_INTO_MY_CODE, - CMD_GET_THREAD_STACK, - CMD_GET_EXCEPTION_DETAILS, - CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION, - CMD_THREAD_RESUME_SINGLE_NOTIFICATION, -) - -from . import RunningTest -from ptvsd.wrapper import UnsupportedPyDevdCommandError, INITIALIZE_RESPONSE - - -def fail(msg): - raise RuntimeError(msg) - - -# TODO: Make sure we are handling all args properly and sending the -# correct response/event bpdies. - -""" -lifecycle (in order), tested via test_lifecycle.py: - -initialize -attach -launch -(setBreakpoints) -(setExceptionBreakpoints) -configurationDone -(normal ops) -disconnect - -Note that setFunctionBreakpoints may also be sent during -configuration, but we do not support function breakpoints. - -normal operations (supported-only): - -threads -stackTrace -scopes -variables -setVariable -evaluate -pause -continue -next -stepIn -stepOut -setBreakpoints -setExceptionBreakpoints -exceptionInfo - -handled PyDevd events: - -CMD_THREAD_CREATE -CMD_THREAD_KILL -CMD_THREAD_SUSPEND -CMD_THREAD_RUN -CMD_SEND_CURR_EXCEPTION_TRACE -CMD_SEND_CURR_EXCEPTION_TRACE_PROCEEDED -""" - - -################################## -# lifecycle requests - -class LifecycleTest(RunningTest): - pass - - -def _get_cmd_version(): - plat = 'WINDOWS' if platform.system() == 'Windows' else 'UNIX' - return '1.1\t%s\tID' % plat - - -class InitializeTests(LifecycleTest, unittest.TestCase): - - @unittest.skip('tested via test_lifecycle.py') - def test_basic(self): - with self.lifecycle.demon_running(port=8888): - req = self.send_request('initialize', { - 'adapterID': 'spam', - }) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.new_response(req, **INITIALIZE_RESPONSE), - self.new_event(1, 'initialized'), - ]) - self.assert_received(self.debugger, []) - - -################################## -# "normal operation" requests - -class NormalRequestTest(RunningTest): - - COMMAND = None - PYDEVD_CMD = None - PYDEVD_RESP = True - - def launched(self, port=8888, **kwargs): - return super(NormalRequestTest, self).launched(port, **kwargs) - - def set_debugger_response(self, *args, **kwargs): - if self.PYDEVD_RESP is None: - return - if self.PYDEVD_RESP is True: - self.PYDEVD_RESP = self.PYDEVD_CMD - self.fix.set_debugger_response( - self.PYDEVD_RESP, - self.pydevd_payload(*args, **kwargs), - reqid=self.PYDEVD_CMD, - ) - - def pydevd_payload(self, *args, **kwargs): - return '' - - def send_request(self, **args): - req = self.fix.send_request(self.COMMAND, args) - if not self.ishidden: - try: - reqs = self.reqs - except AttributeError: - reqs = self.reqs = [] - reqs.append(req) - return req - - def _next_request(self): - return self.reqs.pop(0) - - def expected_response(self, **body): - return self.new_response( - self._next_request(), - **body - ) - - def expected_failure(self, err, **body): - return self.new_failure( - self._next_request(), - err, - seq=None, - **body - ) - - def expected_pydevd_request(self, *args): - return self.debugger_msgs.new_request(self.PYDEVD_CMD, *args) - - -class RestartTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'restart' - - def test_unsupported(self): - with self.launched(): - self.send_request() - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_failure('Unknown command'), - ]) - self.assert_received(self.debugger, []) - - -class ThreadsTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'threads' - PYDEVD_CMD = CMD_LIST_THREADS - PYDEVD_RESP = CMD_RETURN - - def pydevd_payload(self, *threads): - return self.debugger_msgs.format_threads(*threads) - - def test_few(self): - with self.launched(default_threads=False): - self.set_debugger_response( - (1, 'MainThread'), - (10, 'spam'), - (11, 'pydevd.eggs'), - (12, 'Thread-12'), - ) - self.send_request() - received = self.vsc.received - - self.assert_vsc_received(received, [ - # MainThread is #1. - self.new_event('thread', threadId=2, reason='started'), - self.new_event('thread', threadId=3, reason='started'), - self.expected_response( - threads=[ - {'id': 1, 'name': 'MainThread'}, - {'id': 2, 'name': 'spam'}, - # Threads named 'pydevd.*' are ignored. - {'id': 3, 'name': 'Thread-12'}, - ], - ), - # no events - ]) - self.assert_received(self.debugger, [ - self.expected_pydevd_request(), - ]) - - def test_none(self): - with self.launched(default_threads=False): - self.set_debugger_response() - self.send_request() - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - threads=[], - ), - # no events - ]) - self.assert_received(self.debugger, [ - self.expected_pydevd_request(), - ]) - - -class StackTraceTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'stackTrace' - PYDEVD_CMD = CMD_GET_THREAD_STACK - - def pydevd_payload(self, threadid, *frames): - return self.debugger_msgs.format_frames(threadid, 'pause', *frames) - - def test_basic(self): - frames = [ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), - (5, 'eggs', 'xyz.py', 2), - ] - with self.launched(): - with self.hidden(): - tid, thread = self.set_thread('x') - self.suspend(thread, CMD_THREAD_SUSPEND, *frames) - self.set_debugger_response(thread.id, *frames) - self.send_request( - threadId=tid, - #startFrame=1, - #levels=1, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - stackFrames=[ - { - 'id': 1, - 'name': 'spam', - 'source': {'path': 'abc.py', 'sourceReference': 0}, - 'line': 10, - 'column': 1, - }, - { - 'id': 2, - 'name': 'eggs', - 'source': {'path': 'xyz.py', 'sourceReference': 0}, - 'line': 2, - 'column': 1, - }, - ], - totalFrames=2, - ), - # no events - ]) - self.assert_received(self.debugger, [ - self.debugger_msgs.new_request(self.PYDEVD_CMD, str(thread.id)), - ]) - - def test_one_frame(self): - frames = [ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), - ] - with self.launched(): - with self.hidden(): - tid, thread = self.set_thread('x') - self.suspend(thread, CMD_THREAD_SUSPEND, *frames) - self.set_debugger_response(thread.id, *frames) - self.send_request( - threadId=tid, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - stackFrames=[ - { - 'id': 1, - 'name': 'spam', - 'source': {'path': 'abc.py', 'sourceReference': 0}, - 'line': 10, - 'column': 1, - }, - ], - totalFrames=1, - ), - # no events - ]) - self.assert_received(self.debugger, [ - self.debugger_msgs.new_request(self.PYDEVD_CMD, str(thread.id)), - ]) - - def test_with_frame_format(self): - frames = [ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), - (5, 'eggs', 'xyz.py', 2), - ] - with self.launched(): - with self.hidden(): - tid, thread = self.set_thread('x') - self.suspend(thread, CMD_THREAD_SUSPEND, *frames) - self.set_debugger_response(thread.id, *frames) - self.send_request( - threadId=tid, - format={ - 'module': True, - 'line': True, - } - #startFrame=1, - #levels=1, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - stackFrames=[ - { - 'id': 1, - 'name': 'abc.spam : 10', - 'source': {'path': 'abc.py', 'sourceReference': 0}, - 'line': 10, - 'column': 1, - }, - { - 'id': 2, - 'name': 'xyz.eggs : 2', - 'source': {'path': 'xyz.py', 'sourceReference': 0}, - 'line': 2, - 'column': 1, - }, - ], - totalFrames=2, - ), - # no events - ]) - self.assert_received(self.debugger, [ - self.debugger_msgs.new_request(self.PYDEVD_CMD, str(thread.id)), - ]) - - def test_no_threads(self): - with self.launched(): - req = self.send_request( - threadId=10, - ) - received = self.vsc.received - - self.assert_vsc_failure(received, [], req) - self.assert_received(self.debugger, []) - - def test_unknown_thread(self): - with self.launched(): - with self.hidden(): - tid, _ = self.set_thread('x') - self.send_request( - threadId=12345, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_failure('Thread 12345 not found'), - ]) - self.assert_received(self.debugger, []) - - -class ScopesTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'scopes' - - def test_basic(self): - with self.launched(): - with self.hidden(): - self.pause('x', *[ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), # VSC frame ID 1 - (5, 'eggs', 'xyz.py', 2), # VSC frame ID 2 - ]) - self.send_request( - frameId=1, - ) - received = self.vsc.received - self.assert_vsc_received(received, [ - self.expected_response( - scopes=[{ - 'name': 'Locals', - 'expensive': False, - 'variablesReference': 1, # matches frame 2 locals - }], - ), - # no events - ]) - self.assert_received(self.debugger, []) - - -class VariablesTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'variables' - PYDEVD_CMD = [ - CMD_GET_FRAME, - CMD_GET_VARIABLE, - ] - - def pydevd_payload(self, *variables): - return self.debugger_msgs.format_variables(*variables) - - def test_locals(self): - class MyType(object): - pass - obj = MyType() - self.PYDEVD_CMD = CMD_GET_FRAME - with self.launched(): - with self.hidden(): - _, thread = self.pause('t', *[ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), # VSC frame ID 1 - (5, 'eggs', 'xyz.py', 2), # VSC frame ID 2 - ]) - self.set_debugger_response( - # (var, value) - ('spam', 'eggs'), - ('ham', [1, 2, 3]), - ('x', True), - ('y', 42), - ('z', obj), - ) - self.send_request( - variablesReference=1, # matches frame locals - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - variables=[ - { - 'evaluateName': 'ham', - 'name': 'ham', - 'type': 'list', - 'value': '[1, 2, 3]', - 'variablesReference': 2, - }, - { - 'evaluateName': 'spam', - 'name': 'spam', - 'type': 'str', - 'value': "'eggs'", - 'presentationHint': { - 'attributes': ['rawString'], - }, - }, - { - 'evaluateName': 'x', - 'name': 'x', - 'type': 'bool', - 'value': 'True', - }, - { - 'evaluateName': 'y', - 'name': 'y', - 'type': 'int', - 'value': '42', - }, - { - 'evaluateName': 'z', - 'name': 'z', - 'type': 'MyType', - 'variablesReference': 3, - 'value': str(obj), - }, - ], - ), - # no events - ]) - self.assert_received(self.debugger, [ - self.expected_pydevd_request('{}\t2\tFRAME'.format(thread.id)), - ]) - - def test_invalid_var_ref(self): - with self.launched(): - with self.hidden(): - _, thread = self.pause('t', *[ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), # VSC frame ID 1 - (5, 'eggs', 'xyz.py', 2), # VSC frame ID 2 - ]) - self.send_request( - # should NOT match variable or frame ID - variablesReference=12345, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_failure('Variable 12345 not found in frame'), - # no events - ]) - - def test_container(self): - self.PYDEVD_CMD = CMD_GET_FRAME - with self.launched(): - with self.hidden(): - _, thread = self.pause('t', *[ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), # VSC frame ID 1 - (5, 'eggs', 'xyz.py', 2), # VSC frame ID 2 - ]) - self.set_debugger_response( - # (var, value) - ('spam', {'x', 'y', 'z'}), - ) - self.send_request( - variablesReference=1, # matches frame locals - ) - self.PYDEVD_CMD = CMD_GET_VARIABLE - self.set_debugger_response( - # (var, value) - ('x', 1), - ('y', 2), - ('z', 3), - ) - self.send_request( - variablesReference=2, # matches container - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - variables=[ - { - 'name': 'x', - 'type': 'int', - 'value': '1', - 'evaluateName': 'spam.x', - }, - { - 'name': 'y', - 'type': 'int', - 'value': '2', - 'evaluateName': 'spam.y', - }, - { - 'name': 'z', - 'type': 'int', - 'value': '3', - 'evaluateName': 'spam.z', - }, - ], - ), - # no events - ]) - self.assert_received(self.debugger, [ - self.expected_pydevd_request( - '{}\t2\tFRAME\tspam'.format(thread.id)), - ]) - - -class SetVariableTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'setVariable' - PYDEVD_CMD = CMD_CHANGE_VARIABLE - PYDEVD_RESP = CMD_RETURN - - def pydevd_payload(self, variable): - return self.debugger_msgs.format_variables(variable) - - def _set_variables(self, varref, *variables): - with self.hidden(): - self.fix.set_debugger_response( - CMD_GET_FRAME, - self.debugger_msgs.format_variables(*variables), - ) - self.fix.send_request('variables', dict( - variablesReference=varref, - )) - - def test_local(self): - with self.launched(): - with self.hidden(): - _, thread = self.pause('t', *[ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), # VSC frame ID 1 - (5, 'eggs', 'xyz.py', 2), # VSC frame ID 2 - ]) - self._set_variables( - 1, # matches frame locals - ('spam', 42), - ) - self.PYDEVD_CMD = CMD_EXEC_EXPRESSION - self.PYDEVD_RESP = CMD_EVALUATE_EXPRESSION - expected = self.expected_pydevd_request( - '{}\t2\tLOCAL\tspam = eggs\t1'.format(thread.id)) - self.set_debugger_response( - ('spam', 'eggs'), - ) - self.PYDEVD_CMD = CMD_EVALUATE_EXPRESSION - self.set_debugger_response( - ('spam', 'eggs'), - ) - self.send_request( - variablesReference=1, # matches frame locals - name='spam', - value='eggs', - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - type='str', - value="'eggs'", - ), - # no events - ]) - self.assert_received(self.debugger, [ - expected, - self.expected_pydevd_request( - '{}\t2\tLOCAL\tspam\t1'.format(thread.id)), - ]) - - def test_invalid_var_ref(self): - with self.launched(): - with self.hidden(): - _, thread = self.pause('t', *[ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), # VSC frame ID 1 - (5, 'eggs', 'xyz.py', 2), # VSC frame ID 2 - ]) - self.send_request( - # should NOT match any variable or frame ID - variablesReference=12345, - name='spam', - value='eggs', - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_failure('Variable 12345 not found in frame'), - # no events - ]) - - def test_container(self): - with self.launched(): - with self.hidden(): - _, thread = self.pause('t', *[ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), # VSC frame ID 1 - (5, 'eggs', 'xyz.py', 2), # VSC frame ID 2 - ]) - self._set_variables( - 1, # matches frame locals - ('spam', {'x': 1}), - ) - self.PYDEVD_CMD = CMD_EXEC_EXPRESSION - self.PYDEVD_RESP = CMD_EVALUATE_EXPRESSION - expected = self.expected_pydevd_request( - '{}\t2\tLOCAL\tspam.x = 2\t1'.format(thread.id)) - self.set_debugger_response( - ('x', 2), - ) - self.PYDEVD_CMD = CMD_EVALUATE_EXPRESSION - self.set_debugger_response( - ('x', 2), - ) - self.send_request( - variablesReference=2, # matches spam - name='x', - value='2', - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - type='int', - value='2', - ), - # no events - ]) - self.assert_received(self.debugger, [ - expected, - self.expected_pydevd_request( - '{}\t2\tLOCAL\tspam.x\t1'.format(thread.id)), - ]) - - -class EvaluateTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'evaluate' - PYDEVD_CMD = CMD_EVALUATE_EXPRESSION - - def pydevd_payload(self, variable): - return self.debugger_msgs.format_variables(variable) - - def test_basic(self): - with self.launched(): - with self.hidden(): - _, thread = self.pause('x', *[ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), # VSC frame ID 1 - (5, 'eggs', 'xyz.py', 2), # VSC frame ID 2 - ]) - self.set_debugger_response( - ('spam + 1', 43), - ) - self.send_request( - frameId=2, - expression='spam + 1', - context=None, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - type='int', - result='43', - ), - # no events - ]) - self.assert_received(self.debugger, [ - self.expected_pydevd_request( - '{}\t5\tLOCAL\tspam + 1\t1'.format(thread.id)), - ]) - - def test_multiline(self): - with self.launched(): - with self.hidden(): - _, thread = self.pause('x', *[ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), # VSC frame ID 1 - (5, 'eggs', 'xyz.py', 2), # VSC frame ID 2 - ]) - expr = dedent(""" - def my_sqr(x): - return x*x - my_sqr(7)""") - self.set_debugger_response( - (expr, 49), - ) - self.send_request( - frameId=2, - expression=expr, - context=None, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - type='int', - result='49', - ), - # no events - ]) - - expr_rec = '@LINE@def my_sqr(x):@LINE@ return x*x@LINE@my_sqr(7)' - self.assert_received(self.debugger, [ - self.expected_pydevd_request( - '{}\t5\tLOCAL\t{}\t1'.format(thread.id, expr_rec)), - ]) - - def test_hover(self): - with self.launched(): - with self.hidden(): - _, thread = self.pause('x', *[ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), # VSC frame ID 1 - (5, 'eggs', 'xyz.py', 2), # VSC frame ID 2 - ]) - self.set_debugger_response( - ('spam + 1', 'err:43'), - ) - self.send_request( - frameId=2, - expression='spam + 1', - context='hover', - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - result=None, - variablesReference=0, - ), - # no events - ]) - self.assert_received(self.debugger, [ - self.expected_pydevd_request( - '{}\t5\tLOCAL\tspam + 1\t1'.format(thread.id)), - ]) - - -class PauseTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'pause' - PYDEVD_CMD = CMD_THREAD_SUSPEND - PYDEVD_RESP = None - - def test_pause(self): - with self.launched(): - with self.hidden(): - threads = self.set_threads('spam', 'eggs', 'abc') - tid, thread = threads[0] - self.send_request( - threadId=tid, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response(), - # no events - ]) - - expected = [self.expected_pydevd_request('*')] - - self.assert_received_unordered_payload(self.debugger, expected) - - -class ContinueTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'continue' - PYDEVD_CMD = CMD_THREAD_RUN - PYDEVD_RESP = None - - def test_basic(self): - frames = [ - (2, 'spam', 'abc.py', 10), - ] - with self.launched(): - with self.hidden(): - tid, thread = self.pause('x', *frames) - self.send_request( - threadId=tid, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response(allThreadsContinued=True), - # no events - ]) - - expected = [self.debugger_msgs.new_request(self.PYDEVD_CMD, '*')] - self.assert_contains(self.debugger.received, expected, - parser=self.debugger.protocol) - - -class NextTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'next' - PYDEVD_CMD = CMD_STEP_OVER - PYDEVD_RESP = None - - def test_basic(self): - with self.launched(): - with self.hidden(): - tid, thread = self.pause('x', *[ - (2, 'spam', 'abc.py', 10), - ]) - self.send_request( - threadId=tid, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response(), - # no events - ]) - self.assert_received(self.debugger, [ - self.expected_pydevd_request(str(thread.id)), - ]) - - -class StepInTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'stepIn' - PYDEVD_CMD = CMD_STEP_INTO_MY_CODE - PYDEVD_RESP = None - - def test_basic(self): - with self.launched(): - with self.hidden(): - tid, thread = self.pause('x', *[ - (2, 'spam', 'abc.py', 10), - ]) - self.send_request( - threadId=tid, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response(), - # no events - ]) - self.assert_received(self.debugger, [ - self.expected_pydevd_request(str(thread.id)), - ]) - - -class StepOutTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'stepOut' - PYDEVD_CMD = CMD_STEP_RETURN - PYDEVD_RESP = None - - def test_basic(self): - with self.launched(): - with self.hidden(): - tid, thread = self.pause('x', *[ - (2, 'spam', 'abc.py', 10), - ]) - self.send_request( - threadId=tid, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response(), - # no events - ]) - self.assert_received(self.debugger, [ - self.expected_pydevd_request(str(thread.id)), - ]) - - -class StepBackTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'stepBack' - - def test_unsupported(self): - with self.launched(): - self.send_request( - threadId=10, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_failure('Unknown command'), - ]) - self.assert_received(self.debugger, []) - - -class ReverseContinueTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'reverseContinue' - - def test_unsupported(self): - with self.launched(): - self.send_request( - threadId=10, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_failure('Unknown command'), - ]) - self.assert_received(self.debugger, []) - - -class RestartFrameTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'restartFrame' - - def test_unsupported(self): - with self.launched(): - self.send_request( - threadId=10, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_failure('Unknown command'), - ]) - self.assert_received(self.debugger, []) - - -class GotoTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'goto' - - def test_unsupported(self): - with self.launched(): - self.send_request( - threadId=10, - targetId=1, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_failure('Unknown command'), - ]) - self.assert_received(self.debugger, []) - - -class SetBreakpointsTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'setBreakpoints' - PYDEVD_CMD = [ - [CMD_REMOVE_BREAK], - [CMD_SET_BREAK], - ] - PYDEVD_RESP = None - - def test_initial(self): - with self.launched(): - self.send_request( - source={'path': 'spam.py'}, - breakpoints=[ - {'line': '10'}, - {'line': '15', - 'condition': 'i == 3'}, - ], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - breakpoints=[ - {'id': 1, - 'verified': True, - 'line': '10'}, - {'id': 2, - 'verified': True, - 'line': '15'}, - ], - ), - # no events - ]) - self.PYDEVD_CMD = CMD_SET_BREAK - self.assert_received(self.debugger, [ - self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa - self.expected_pydevd_request( - '2\tpython-line\tspam.py\t15\tNone\ti == 3\tNone\tNone\tNone\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - ]) - - def test_with_hit_condition(self): - with self.launched(): - self.send_request( - source={'path': 'spam.py'}, - breakpoints=[ - {'line': '10', - 'hitCondition': '5'}, - {'line': '15', - 'hitCondition': ' ==5'}, - {'line': '20', - 'hitCondition': '> 5'}, - {'line': '25', - 'hitCondition': '% 5'}, - {'line': '30', - 'hitCondition': 'x'} - ], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - breakpoints=[ - {'id': 1, - 'verified': True, - 'line': '10'}, - {'id': 2, - 'verified': True, - 'line': '15'}, - {'id': 3, - 'verified': True, - 'line': '20'}, - {'id': 4, - 'verified': True, - 'line': '25'}, - {'id': 5, - 'verified': True, - 'line': '30'}, - ], - ), - # no events - ]) - self.PYDEVD_CMD = CMD_SET_BREAK - self.assert_received(self.debugger, [ - self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\t@HIT@ == 5\tNone\tALL'), # noqa - self.expected_pydevd_request( - '2\tpython-line\tspam.py\t15\tNone\tNone\tNone\t@HIT@ ==5\tNone\tALL'), # noqa - self.expected_pydevd_request( - '3\tpython-line\tspam.py\t20\tNone\tNone\tNone\t@HIT@ > 5\tNone\tALL'), # noqa - self.expected_pydevd_request( - '4\tpython-line\tspam.py\t25\tNone\tNone\tNone\t@HIT@ % 5 == 0\tNone\tALL'), # noqa - self.expected_pydevd_request( - '5\tpython-line\tspam.py\t30\tNone\tNone\tNone\tx\tNone\tALL'), - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - ]) - - def test_with_logpoint(self): - with self.launched(): - self.send_request( - source={'path': 'spam.py'}, - breakpoints=[ - {'line': '10', - 'logMessage': '5'}, - {'line': '15', - 'logMessage': 'Hello World'}, - {'line': '20', - 'logMessage': '{a}'}, - {'line': '25', - 'logMessage': '{a}+{b}=Something'} - ], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - breakpoints=[ - {'id': 1, - 'verified': True, - 'line': '10'}, - {'id': 2, - 'verified': True, - 'line': '15'}, - {'id': 3, - 'verified': True, - 'line': '20'}, - {'id': 4, - 'verified': True, - 'line': '25'} - ], - ), - # no events - ]) - self.PYDEVD_CMD = CMD_SET_BREAK - self.assert_received(self.debugger, [ - self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\t' + repr("5") + '\tNone\tTrue\tALL'), # noqa - self.expected_pydevd_request( - '2\tpython-line\tspam.py\t15\tNone\tNone\t' + repr("Hello World") + '\tNone\tTrue\tALL'), # noqa - self.expected_pydevd_request( - '3\tpython-line\tspam.py\t20\tNone\tNone\t"{}".format(a)\tNone\tTrue\tALL'), # noqa - self.expected_pydevd_request( - '4\tpython-line\tspam.py\t25\tNone\tNone\t"{}+{}=Something".format(a, b)\tNone\tTrue\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - ]) - - def test_with_existing(self): - with self.launched(): - with self.hidden(): - self.PYDEVD_CMD = CMD_SET_BREAK - p1 = self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone\tALL') # noqa - p2 = self.expected_pydevd_request( - '2\tpython-line\tspam.py\t17\tNone\tNone\tNone\tNone\tNone\tALL') # noqa - with self.expect_debugger_command(CMD_VERSION): - self.fix.send_request('setBreakpoints', dict( - source={'path': 'spam.py'}, - breakpoints=[ - {'line': '10'}, - {'line': '17'}, - ], - )) - self.wait_for_pydevd(p1, p2) - self.send_request( - source={'path': 'spam.py'}, - breakpoints=[ - {'line': '113'}, - {'line': '2'}, - {'line': '10'}, # a repeat - ], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - breakpoints=[ - {'id': 3, - 'verified': True, - 'line': '113'}, - {'id': 4, - 'verified': True, - 'line': '2'}, - {'id': 5, - 'verified': True, - 'line': '10'}, - ], - ), - # no events - ]) - self.PYDEVD_CMD = CMD_REMOVE_BREAK - if self.debugger.received[0].payload.endswith('1'): - removed = [ - self.expected_pydevd_request('python-line\tspam.py\t1'), - self.expected_pydevd_request('python-line\tspam.py\t2'), - ] - else: - removed = [ - self.expected_pydevd_request('python-line\tspam.py\t2'), - self.expected_pydevd_request('python-line\tspam.py\t1'), - ] - self.PYDEVD_CMD = CMD_SET_BREAK - self.assert_received(self.debugger, removed + [ - self.expected_pydevd_request( - '3\tpython-line\tspam.py\t113\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa - self.expected_pydevd_request( - '4\tpython-line\tspam.py\t2\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa - self.expected_pydevd_request( - '5\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - ]) - - def test_multiple_files(self): - with self.launched(): - self.send_request( - source={'path': 'spam.py'}, - breakpoints=[{'line': '10'}], - ) - self.send_request( - source={'path': 'eggs.py'}, - breakpoints=[{'line': '17'}], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - breakpoints=[ - {'id': 1, - 'verified': True, - 'line': '10'}, - ], - ), - self.expected_response( - breakpoints=[ - {'id': 2, - 'verified': True, - 'line': '17'}, - ], - ), - # no events - ]) - - self.PYDEVD_CMD = CMD_SET_BREAK - self.assert_received(self.debugger, [ - self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - self.expected_pydevd_request( - '2\tpython-line\teggs.py\t17\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - ]) - - def test_vs_django(self): - with self.launched(args={'options': 'DJANGO_DEBUG=True'}): - self.send_request( - source={'path': 'spam.py'}, - breakpoints=[{'line': '10'}], - ) - self.send_request( - source={'path': 'eggs.html'}, - breakpoints=[{'line': '17'}], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - breakpoints=[ - {'id': 1, - 'verified': True, - 'line': '10'}, - ], - ), - self.expected_response( - breakpoints=[ - {'id': 2, - 'verified': True, - 'line': '17'}, - ], - ), - ]) - - self.PYDEVD_CMD = CMD_SET_BREAK - self.assert_received(self.debugger, [ - self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - self.expected_pydevd_request( - '2\tdjango-line\teggs.html\t17\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - ]) - - def test_vs_django_logpoint(self): - with self.launched(args={'options': 'DJANGO_DEBUG=True'}): - self.send_request( - source={'path': 'spam.py'}, - breakpoints=[{'line': '10', 'logMessage': 'Hello World'}], - ) - self.send_request( - source={'path': 'eggs.html'}, - breakpoints=[{'line': '17', 'logMessage': 'Hello Django World'}], # noqa - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - breakpoints=[ - {'id': 1, - 'verified': True, - 'line': '10'}, - ], - ), - self.expected_response( - breakpoints=[ - {'id': 2, - 'verified': True, - 'line': '17'}, - ], - ), - ]) - - self.PYDEVD_CMD = CMD_SET_BREAK - self.assert_received(self.debugger, [ - self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\t' + repr("Hello World") + '\tNone\tTrue\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - self.expected_pydevd_request( - '2\tdjango-line\teggs.html\t17\tNone\tNone\t' + repr("Hello Django World") + '\tNone\tTrue\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - ]) - - def test_vs_flask_jinja2(self): - with self.launched(args={'options': 'FLASK_DEBUG=True'}): - self.send_request( - source={'path': 'spam.py'}, - breakpoints=[{'line': '10'}], - ) - self.send_request( - source={'path': 'eggs.html'}, - breakpoints=[{'line': '17'}], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - breakpoints=[ - {'id': 1, - 'verified': True, - 'line': '10'}, - ], - ), - self.expected_response( - breakpoints=[ - {'id': 2, - 'verified': True, - 'line': '17'}, - ], - ), - ]) - - self.PYDEVD_CMD = CMD_SET_BREAK - self.assert_received(self.debugger, [ - self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - self.expected_pydevd_request( - '2\tjinja2-line\teggs.html\t17\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - ]) - - def test_vs_flask_jinja2_logpoint(self): - with self.launched(args={'options': 'FLASK_DEBUG=True'}): - self.send_request( - source={'path': 'spam.py'}, - breakpoints=[{'line': '10', 'logMessage': 'Hello World'}], - ) - self.send_request( - source={'path': 'eggs.html'}, - breakpoints=[{'line': '17', 'logMessage': 'Hello Jinja World'}], # noqa - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - breakpoints=[ - {'id': 1, - 'verified': True, - 'line': '10'}, - ], - ), - self.expected_response( - breakpoints=[ - {'id': 2, - 'verified': True, - 'line': '17'}, - ], - ), - ]) - - self.PYDEVD_CMD = CMD_SET_BREAK - self.assert_received(self.debugger, [ - self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\t' + repr("Hello World") + '\tNone\tTrue\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - self.expected_pydevd_request( - '2\tjinja2-line\teggs.html\t17\tNone\tNone\t' + repr("Hello Jinja World") + '\tNone\tTrue\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - ]) - - def test_vsc_flask_jinja2(self): - with self.launched(args={'debugOptions': ['Flask']}): - self.send_request( - source={'path': 'spam.py'}, - breakpoints=[{'line': '10'}], - ) - self.send_request( - source={'path': 'eggs.html'}, - breakpoints=[{'line': '17'}], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - breakpoints=[ - {'id': 1, - 'verified': True, - 'line': '10'}, - ], - ), - self.expected_response( - breakpoints=[ - {'id': 2, - 'verified': True, - 'line': '17'}, - ], - ), - ]) - - self.PYDEVD_CMD = CMD_SET_BREAK - self.assert_received(self.debugger, [ - self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - self.expected_pydevd_request( - '2\tjinja2-line\teggs.html\t17\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - ]) - - def test_vsc_jinja2(self): - with self.launched(args={'debugOptions': ['Jinja']}): - self.send_request( - source={'path': 'spam.py'}, - breakpoints=[{'line': '10'}], - ) - self.send_request( - source={'path': 'eggs.html'}, - breakpoints=[{'line': '17'}], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - breakpoints=[ - {'id': 1, - 'verified': True, - 'line': '10'}, - ], - ), - self.expected_response( - breakpoints=[ - {'id': 2, - 'verified': True, - 'line': '17'}, - ], - ), - ]) - - self.PYDEVD_CMD = CMD_SET_BREAK - self.assert_received(self.debugger, [ - self.expected_pydevd_request( - '1\tpython-line\tspam.py\t10\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - self.expected_pydevd_request( - '2\tjinja2-line\teggs.html\t17\tNone\tNone\tNone\tNone\tNone\tALL'), # noqa - self.debugger_msgs.new_request(CMD_VERSION, _get_cmd_version()), - ]) - - -class SetFunctionBreakpointsTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'setFunctionBreakpoints' - - def test_unsupported(self): - with self.launched(): - self.send_request( - breakpoints=[ - { - 'name': 'spam', - }, - ], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_failure('Unknown command'), - ]) - self.assert_received(self.debugger, []) - - -class SetExceptionBreakpointsTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'setExceptionBreakpoints' - PYDEVD_CMD = [ - [CMD_REMOVE_EXCEPTION_BREAK], - [CMD_ADD_EXCEPTION_BREAK], - ] - PYDEVD_RESP = None - - def _check_options(self, options, expectedpydevd): - with self.launched(): - self.send_request( - filters=[], - exceptionOptions=options, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response(), - ]) - self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK - self.assert_received( - self.debugger, - [self.expected_pydevd_request(expect) - for expect in expectedpydevd], - ) - - def _check_option(self, paths, mode, expectedpydevd): - options = [{ - 'path': paths, - 'breakMode': mode, - }] - self._check_options(options, expectedpydevd) - - # TODO: We've hard-coded the currently supported modes. If other - # modes are added later then we need to add more tests. We don't - # have a programmatic alternative that is very readable. - - # NOTE: The mode here depends on the default value of DEBUG_STDLIB. - # When this test was written it was assumed the DEBUG_STDLIB = False. - # this means ignore_stdlib arg to pydevd must be 1 - - def test_single_option_single_path_mode_never(self): - path = { - 'names': ['Python Exceptions'], - } - self._check_option( - [path], - 'never', - ['python-BaseException\t0\t0\t1'], - ) - - def test_single_option_single_path_mode_always(self): - path = { - 'names': ['Python Exceptions'], - } - self._check_option( - [path], - 'always', - ['python-BaseException\t1\t0\t1'], - ) - - def test_single_option_single_path_mode_unhandled(self): - path = { - 'names': ['Python Exceptions'], - } - self._check_option( - [path], - 'unhandled', - ['python-BaseException\t0\t1\t1'], - ) - - def test_single_option_single_path_mode_userUnhandled(self): - path = { - 'names': ['Python Exceptions'], - } - self._check_option( - [path], - 'userUnhandled', - ['python-BaseException\t0\t1\t1'], - ) - - def test_single_option_empty_paths(self): - self._check_option([], 'userUnhandled', []) - - def test_single_option_single_path_python_exception(self): - path = { - 'names': ['ImportError'], - } - self._check_option( - [path], - 'userUnhandled', - [], - ) - - def test_single_option_single_path_not_python_category(self): - path = { - 'names': ['not Python Exceptions'], - } - self._check_option( - [path], - 'userUnhandled', - [], - ) - - # TODO: verify behavior - @unittest.skip('poorly specified') - def test_single_option_single_path_multiple_names(self): - path = { - 'names': [ - 'Python Exceptions', - # The rest are ignored by ptvsd? VSC? - 'spam', - 'eggs' - ], - } - self._check_option( - [path], - 'always', - ['python-BaseException\t3\t0\t1'], - ) - - def test_single_option_shallow_path(self): - path = [ - {'names': ['Python Exceptions']}, - {'names': ['ImportError']}, - ] - self._check_option(path, 'always', [ - 'python-ImportError\t1\t0\t1', - ]) - - def test_single_option_deep_path(self): - path = [ - {'names': ['Python Exceptions']}, - {'names': ['ImportError']}, - {'names': ['RuntimeError']}, - {'names': ['ValueError']}, - {'names': ['MyError']}, - ] - self._check_option(path, 'always', [ - 'python-ImportError\t1\t0\t1', - 'python-RuntimeError\t1\t0\t1', - 'python-ValueError\t1\t0\t1', - 'python-MyError\t1\t0\t1', - ]) - - # TODO: verify behavior - @unittest.skip('poorly specified') - def test_single_option_multiple_names(self): - path = [ - {'names': ['Python Exceptions']}, - {'names': ['ImportError', 'RuntimeError', 'ValueError']}, - ] - self._check_option(path, 'always', [ - 'python-ImportError\t1\t0\t1', - 'python-RuntimeError\t1\t0\t1', - 'python-ValueError\t1\t0\t1', - ]) - - # TODO: verify behavior - @unittest.skip('poorly specified') - def test_single_option_first_path_not_category(self): - self._check_option( - [ - {'names': ['not Python Exceptions']}, - {'names': ['other']}, - ], - 'always', - [], - ) - - # TODO: verify behavior - @unittest.skip('poorly specified') - def test_single_option_unknown_exception(self): - path = [ - {'names': ['Python Exceptions']}, - {'names': ['AnUnknownException']}, - ] - with self.assertRaises(ValueError): - self._check_option(path, 'always', []) - - def test_multiple_options(self): - options = [ - # shallow path - {'path': [ - {'names': ['Python Exceptions']}, - {'names': ['ImportError']}, - ], - 'breakMode': 'always'}, - # ignored - {'path': [ - {'names': ['non-Python Exceptions']}, - {'names': ['OSError']}, - ], - 'breakMode': 'always'}, - # deep path - {'path': [ - {'names': ['Python Exceptions']}, - {'names': ['ModuleNotFoundError']}, - {'names': ['RuntimeError']}, - {'names': ['MyError']}, - ], - 'breakMode': 'unhandled'}, - # multiple names - {'path': [ - {'names': ['Python Exceptions']}, - {'names': ['ValueError', 'IndexError']}, - ], - 'breakMode': 'never'}, - # catch-all - {'path': [ - {'names': ['Python Exceptions']}, - ], - 'breakMode': 'userUnhandled'}, - ] - self._check_options(options, [ - # shallow path - 'python-ImportError\t1\t0\t1', - # ignored - # deep path - 'python-ModuleNotFoundError\t0\t1\t1', - 'python-RuntimeError\t0\t1\t1', - 'python-MyError\t0\t1\t1', - # multiple names - 'python-ValueError\t0\t0\t1', - 'python-IndexError\t0\t0\t1', - # catch-all - 'python-BaseException\t0\t1\t1', - ]) - - def test_options_with_existing_filters(self): - with self.launched(): - with self.hidden(): - self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK - p1 = self.expected_pydevd_request( - 'python-BaseException\t0\t1\t1', - ) - self.fix.send_request('setExceptionBreakpoints', dict( - filters=[ - 'uncaught', - ], - )) - self.wait_for_pydevd(p1) - self.send_request( - filters=[], - exceptionOptions=[ - {'path': [ - {'names': ['Python Exceptions']}, - {'names': ['ImportError']}, - ], - 'breakMode': 'never'}, - {'path': [ - {'names': ['Python Exceptions']}, - {'names': ['RuntimeError']}, - ], - 'breakMode': 'always'}, - ] - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response(), - ]) - self.PYDEVD_CMD = CMD_REMOVE_EXCEPTION_BREAK - removed = [ - self.expected_pydevd_request('python-BaseException'), - ] - self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK - self.assert_received(self.debugger, removed + [ - self.expected_pydevd_request('python-ImportError\t0\t0\t1'), - self.expected_pydevd_request('python-RuntimeError\t1\t0\t1'), - ]) - - def test_options_with_existing_options(self): - with self.launched(): - with self.hidden(): - self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK - p1 = self.expected_pydevd_request( - 'python-ImportError\t0\t1\t1', - ) - p2 = self.expected_pydevd_request( - 'python-BaseException\t1\t0\t1', - ) - self.fix.send_request('setExceptionBreakpoints', dict( - filters=[], - exceptionOptions=[ - {'path': [ - {'names': ['Python Exceptions']}, - {'names': ['ImportError']}, - ], - 'breakMode': 'unhandled'}, - {'path': [ - {'names': ['Python Exceptions']}, - ], - 'breakMode': 'always'}, - ], - )) - self.wait_for_pydevd(p1, p2) - self.send_request( - filters=[], - exceptionOptions=[ - {'path': [ - {'names': ['Python Exceptions']}, - {'names': ['ImportError']}, - ], - 'breakMode': 'never'}, - {'path': [ - {'names': ['Python Exceptions']}, - {'names': ['RuntimeError']}, - ], - 'breakMode': 'unhandled'}, - ] - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response(), - ]) - self.PYDEVD_CMD = CMD_REMOVE_EXCEPTION_BREAK - if self.debugger.received[0].payload == 'python-ImportError': - removed = [ - self.expected_pydevd_request('python-ImportError'), - self.expected_pydevd_request('python-BaseException'), - ] - else: - removed = [ - self.expected_pydevd_request('python-BaseException'), - self.expected_pydevd_request('python-ImportError'), - ] - self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK - self.assert_received(self.debugger, removed + [ - self.expected_pydevd_request('python-ImportError\t0\t0\t1'), - self.expected_pydevd_request('python-RuntimeError\t0\t1\t1'), - ]) - - # TODO: As with the option modes, we've hard-coded the filters - # in the following tests. If the supported filters change then - # we must adjust/extend the tests. - - def test_single_filter_raised(self): - with self.launched(): - self.send_request( - filters=[ - 'raised', - ], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response(), - ]) - self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK - self.assert_received(self.debugger, [ - self.expected_pydevd_request('python-BaseException\t1\t0\t1'), - ]) - - def test_single_filter_uncaught(self): - with self.launched(): - self.send_request( - filters=[ - 'uncaught', - ], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response(), - ]) - self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK - self.assert_received(self.debugger, [ - self.expected_pydevd_request('python-BaseException\t0\t1\t1'), - ]) - - def test_multiple_filters_all(self): - with self.launched(): - self.send_request( - filters=[ - 'uncaught', - 'raised', - ], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response(), - ]) - self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK - self.assert_received(self.debugger, [ - self.expected_pydevd_request('python-BaseException\t1\t1\t1'), - ]) - - def test_multiple_filters_repeat(self): - with self.launched(): - self.send_request( - filters=[ - 'raised', - 'raised', - ], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response(), - ]) - self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK - self.assert_received(self.debugger, [ - self.expected_pydevd_request('python-BaseException\t1\t0\t1'), - ]) - - def test_empty_filters(self): - with self.launched(): - self.send_request( - filters=[], - ) - - self.assert_received(self.vsc, [ - self.expected_response() - # no events - ]) - self.assert_received(self.debugger, []) - - def test_filters_with_existing_filters(self): - with self.launched(): - with self.hidden(): - self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK - p1 = self.expected_pydevd_request( - 'python-BaseException\t0\t1\t1', - ) - self.fix.send_request('setExceptionBreakpoints', dict( - filters=[ - 'uncaught', - ], - )) - self.wait_for_pydevd(p1) - self.send_request( - filters=[ - 'raised', - ], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response(), - ]) - self.PYDEVD_CMD = CMD_REMOVE_EXCEPTION_BREAK - removed = [ - self.expected_pydevd_request('python-BaseException'), - ] - self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK - self.assert_received(self.debugger, removed + [ - self.expected_pydevd_request('python-BaseException\t1\t0\t1'), - ]) - - def test_filters_with_existing_options(self): - with self.launched(): - with self.hidden(): - self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK - p1 = self.expected_pydevd_request( - 'python-ImportError\t0\t1\t1', - ) - p2 = self.expected_pydevd_request( - 'python-BaseException\t1\t0\t1', - ) - self.fix.send_request('setExceptionBreakpoints', dict( - filters=[], - exceptionOptions=[ - {'path': [ - {'names': ['Python Exceptions']}, - {'names': ['ImportError']}, - ], - 'breakMode': 'unhandled'}, - {'path': [ - {'names': ['Python Exceptions']}, - ], - 'breakMode': 'always'}, - ], - )) - self.wait_for_pydevd(p1, p2) - self.send_request( - filters=[ - 'raised', - ], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response(), - ]) - self.PYDEVD_CMD = CMD_REMOVE_EXCEPTION_BREAK - if self.debugger.received[0].payload == 'python-ImportError': - removed = [ - self.expected_pydevd_request('python-ImportError'), - self.expected_pydevd_request('python-BaseException'), - ] - else: - removed = [ - self.expected_pydevd_request('python-BaseException'), - self.expected_pydevd_request('python-ImportError'), - ] - self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK - self.assert_received(self.debugger, removed + [ - self.expected_pydevd_request('python-BaseException\t1\t0\t1'), - ]) - - def test_filters_with_empty_options(self): - with self.launched(): - self.send_request( - filters=[ - 'raised', - ], - exceptionOptions=[], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response(), - ]) - self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK - self.assert_received(self.debugger, [ - self.expected_pydevd_request('python-BaseException\t1\t0\t1'), - ]) - - # TODO: verify behavior - @unittest.skip('poorly specified') - def test_options_and_filters_both_provided(self): - with self.launched(): - self.send_request( - filters=[ - 'raised', - ], - exceptionOptions=[ - {'path': [ - {'names': ['Python Exceptions']}, - {'names': ['ImportError']}, - ], - 'breakMode': 'unhandled'}, - ], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response(), - ]) - self.PYDEVD_CMD = CMD_ADD_EXCEPTION_BREAK - self.assert_received(self.debugger, [ - 'python-BaseException\t1\t0\t1', - 'python-ImportError\t0\t1\t1', - ]) - - -class ExceptionInfoTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'exceptionInfo' - - # modes: ['never', 'always', 'unhandled', 'userUnhandled'] - # - # min response: - # exceptionId='', - # breakMode='', - # - # max response: - # exceptionId='', - # description='', - # breakMode='', - # details=dict( - # message='', - # typeName='', - # fullTypeName='', - # evaluateName='', - # stackTrace='', - # innerException=[ - # # details - # # details - # # ... - # ], - # ), - - PYDEVD_CMD = CMD_GET_EXCEPTION_DETAILS - - def pydevd_payload(self, threadid, *args): - if self.PYDEVD_CMD == CMD_GET_EXCEPTION_DETAILS: - return self.debugger_msgs.format_exception_details( - threadid, args[0], *args[1:]) - else: - return self.debugger_msgs.format_variables(*args) - - def test_active_exception(self): - exc = RuntimeError('something went wrong') - lineno = fail.__code__.co_firstlineno + 1 - frame = (2, 'fail', __file__, lineno) # (pfid, func, file, line) - with self.launched(): - with self.hidden(): - tid, thread = self.error('x', exc, frame) - self.set_debugger_response(thread.id, exc, frame) - self.send_request( - threadId=tid, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - exceptionId='RuntimeError', - description='something went wrong', - breakMode='unhandled', - details=dict( - message='something went wrong', - typeName='RuntimeError', - source=__file__, - stackTrace='\n'.join([ - ' File "{}", line {}, in fail'.format(__file__, - lineno), - ' raise RuntimeError(msg)', - '', - ]), - ), - ), - ]) - self.assert_received(self.debugger, [ - self.debugger_msgs.new_request( - CMD_GET_EXCEPTION_DETAILS, str(thread.id)), - ]) - - def test_no_exception(self): - exc = RuntimeError('something went wrong') - lineno = fail.__code__.co_firstlineno + 1 - frame = (2, 'fail', __file__, lineno) # (pfid, func, file, line) - with self.launched(): - with self.hidden(): - tid, thread = self.error('x', exc, frame) - self.set_debugger_response(thread.id, exc, frame) - self.send_request( - threadId=tid, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - exceptionId='RuntimeError', - description='something went wrong', - breakMode='unhandled', - details=dict( - typeName='RuntimeError', - message='something went wrong', - source=__file__, - stackTrace='\n'.join([ - ' File "{}", line {}, in fail'.format(__file__, - lineno), - ' raise RuntimeError(msg)', - '', - ]), - ), - ), - ]) - self.assert_received(self.debugger, [ - self.debugger_msgs.new_request( - CMD_GET_EXCEPTION_DETAILS, str(thread.id)), - ]) - - -class RunInTerminalTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'runInTerminal' - - def test_unsupported(self): - with self.launched(): - self.send_request( - cwd='.', - args=['spam'], - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_failure('Unknown command'), - ]) - self.assert_received(self.debugger, []) - - -class SourceTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'source' - - def test_unsupported(self): - with self.launched(): - self.send_request( - sourceReference=0, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_failure('Source unavailable'), - ]) - self.assert_received(self.debugger, []) - - -class ModulesTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'modules' - - def test_no_modules(self): - with self.launched(): - self.send_request() - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_response( - modules=[], - totalModules=0 - ), - ]) - self.assert_received(self.debugger, []) - - -class LoadedSourcesTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'loadedSources' - - def test_unsupported(self): - with self.launched(): - self.send_request() - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_failure('Unknown command'), - ]) - self.assert_received(self.debugger, []) - - -class StepInTargetsTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'stepInTargets' - - def test_unsupported(self): - with self.launched(): - self.send_request( - frameId=1, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_failure('Unknown command'), - ]) - self.assert_received(self.debugger, []) - - -class GotoTargetsTests(NormalRequestTest, unittest.TestCase): - - COMMAND = 'gotoTargets' - - def test_unsupported(self): - with self.launched(): - self.send_request( - source={}, - line=0, - ) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_failure('Unknown command'), - ]) - self.assert_received(self.debugger, []) - - -################################## -# VSC events - -# These events are emitted by ptvsd: -# -# initialized -# - after "initialize" response -# exited -# - at close -# terminated -# - at close -# stopped -# - in response to CMD_THREAD_SUSPEND -# continued -# - in response to CMD_THREAD_RUN -# thread -# - in response to CMD_THREAD_CREATE -# - in response to CMD_THREAD_KILL -# - with "threads" response (if new) -# process -# - at the end of initialization (after "configurationDone" response) - -# These events are never emitted by ptvsd: -# -# output -# breakpoint -# module -# loadedSource -# capabilities - - -################################## -# handled PyDevd events - -class PyDevdEventTest(RunningTest): - - CMD = None - EVENT = None - - def pydevd_payload(self, *args, **kwargs): - return '' - - def launched(self, port=8888, **kwargs): - kwargs.setdefault('default_threads', False) - return super(PyDevdEventTest, self).launched(port, **kwargs) - - def send_event(self, *args, **kwargs): - handler = kwargs.pop('handler', None) - text = self.pydevd_payload(*args, **kwargs) - self.fix.send_event(self.CMD, text, self.EVENT, handler=handler) - - def expected_event(self, **body): - return self.new_event(self.EVENT, seq=None, **body) - - -class ExitEventTests(PyDevdEventTest, unittest.TestCase): - - CMD = CMD_EXIT - EVENT = None - - def pydevd_payload(self): - return '' - - def test_unsupported(self): - with self.launched(): - with self.assertRaises(UnsupportedPyDevdCommandError): - self.send_event() - received = self.vsc.received - - self.assert_vsc_received(received, []) - self.assert_received(self.debugger, []) - - -class ThreadEventTest(PyDevdEventTest): - - _tid = None - - def send_event(self, *args, **kwargs): - def handler(msg, _): - self._tid = msg.body['threadId'] - kwargs['handler'] = handler - super(ThreadEventTest, self).send_event(*args, **kwargs) - return self._tid - - -class ThreadCreateEventTests(ThreadEventTest, unittest.TestCase): - - CMD = CMD_THREAD_CREATE - EVENT = 'thread' - - def pydevd_payload(self, threadid, name): - thread = (threadid, name) - return self.debugger_msgs.format_threads(thread) - - def test_launched(self): - with self.launched(process=False): - with self.wait_for_event('process'): - tid = self.send_event(10, 'spam') - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.new_event('process', **dict( - name=sys.argv[0], - systemProcessId=os.getpid(), - isLocalProcess=True, - startMethod='launch', - )), - self.expected_event( - reason='started', - threadId=tid, - ), - ]) - self.assert_received(self.debugger, []) - - @unittest.skip('currently not supported') - def test_attached(self): - with self.attached(8888, process=False): - with self.wait_for_event('process'): - tid = self.send_event(10, 'spam') - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.new_event('process', **dict( - name=sys.argv[0], - systemProcessId=os.getpid(), - isLocalProcess=True, - startMethod='attach', - )), - self.expected_event( - reason='started', - threadId=tid, - ), - ]) - self.assert_received(self.debugger, []) - - def test_process_one_off(self): - with self.launched(process=False): - with self.wait_for_event('process'): - tid1 = self.send_event(10, 'spam') - tid2 = self.send_event(11, 'eggs') - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.new_event('process', **dict( - name=sys.argv[0], - systemProcessId=os.getpid(), - isLocalProcess=True, - startMethod='launch', - )), - self.expected_event( - reason='started', - threadId=tid1, - ), - self.expected_event( - reason='started', - threadId=tid2, - ), - ]) - self.assert_received(self.debugger, []) - - # TODO: verify behavior - @unittest.skip('poorly specified') - def test_exists(self): - with self.launched(): - with self.hidden(): - _, thread = self.set_thread('x') - self.send_event(thread.id, 'spam') - received = self.vsc.received - - self.assert_vsc_received(received, []) - self.assert_received(self.debugger, []) - - def test_pydevd_name(self): - self.EVENT = None - with self.launched(): - self.send_event(10, 'pydevd.spam') - received = self.vsc.received - - self.assert_vsc_received(received, []) - self.assert_received(self.debugger, []) - - def test_ptvsd_name(self): - self.EVENT = None - with self.launched(): - self.send_event(10, 'ptvsd.spam') - received = self.vsc.received - - self.assert_vsc_received(received, []) - self.assert_received(self.debugger, []) - - -class ThreadKillEventTests(ThreadEventTest, unittest.TestCase): - - CMD = CMD_THREAD_KILL - EVENT = 'thread' - - def pydevd_payload(self, threadid): - return str(threadid) - - def test_known(self): - with self.launched(): - with self.hidden(): - tid, thread = self.set_thread('x') - self.send_event(thread.id) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_event( - reason='exited', - threadId=tid, - ), - ]) - self.assert_received(self.debugger, []) - - def test_unknown(self): - self.EVENT = None - with self.launched(): - self.send_event(10) - received = self.vsc.received - - self.assert_vsc_received(received, []) - self.assert_received(self.debugger, []) - - def test_pydevd_name(self): - self.EVENT = None - with self.launched(): - with self.hidden(): - _, thread = self.set_thread('pydevd.spam') - self.send_event(thread.id) - received = self.vsc.received - - self.assert_vsc_received(received, []) - self.assert_received(self.debugger, []) - - def test_ptvsd_name(self): - self.EVENT = None - with self.launched(): - with self.hidden(): - _, thread = self.set_thread('ptvsd.spam') - self.send_event(thread.id) - received = self.vsc.received - - self.assert_vsc_received(received, []) - self.assert_received(self.debugger, []) - - -class ThreadSuspendEventTests(ThreadEventTest, unittest.TestCase): - - CMD = CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION - EVENT = 'stopped' - - def pydevd_payload(self, threadid, reason, *frames): - if not frames: - frames = [ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), - (5, 'eggs', 'xyz.py', 2), - ] - return self.debugger_msgs.format_frames2(threadid, reason, *frames) - - def test_step_into(self): - with self.launched(): - with self.hidden(): - _, thread = self.set_thread('x') - tid = self.send_event(thread.id, CMD_STEP_INTO) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_event( - reason='step', - threadId=tid, - text=None, - description=None, - preserveFocusHint=False, - ), - ]) - self.assert_received(self.debugger, []) - - def test_step_over(self): - with self.launched(): - with self.hidden(): - _, thread = self.set_thread('x') - tid = self.send_event(thread.id, CMD_STEP_OVER) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_event( - reason='step', - threadId=tid, - text=None, - description=None, - preserveFocusHint=False, - ), - ]) - self.assert_received(self.debugger, []) - - def test_step_return(self): - with self.launched(): - with self.hidden(): - _, thread = self.set_thread('x') - tid = self.send_event(thread.id, CMD_STEP_RETURN) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_event( - reason='step', - threadId=tid, - text=None, - description=None, - preserveFocusHint=False, - ), - ]) - self.assert_received(self.debugger, []) - - def test_caught_exception(self): - exc = RuntimeError('something went wrong') - with self.launched(): - with self.hidden(): - _, thread = self.set_thread('x') - self.set_debugger_response( - CMD_GET_EXCEPTION_DETAILS, - self.debugger_msgs.format_exception_details( - thread.id, exc - ), - ) - tid = self.send_event(thread.id, CMD_STEP_CAUGHT_EXCEPTION) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_event( - reason='exception', - threadId=tid, - text='RuntimeError', - description='something went wrong', - preserveFocusHint=False, - ), - ]) - self.assert_received(self.debugger, [ - self.debugger_msgs.new_request( - CMD_GET_EXCEPTION_DETAILS, - str(thread.id)), - ]) - - def test_exception_break(self): - exc = RuntimeError('something went wrong') - with self.launched(): - with self.hidden(): - _, thread = self.set_thread('x') - self.set_debugger_response( - CMD_GET_EXCEPTION_DETAILS, - self.debugger_msgs.format_exception_details( - thread.id, exc - ), - ) - tid = self.send_event(thread.id, CMD_ADD_EXCEPTION_BREAK) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_event( - reason='exception', - threadId=tid, - text='RuntimeError', - description='something went wrong', - preserveFocusHint=False, - ), - ]) - self.assert_received(self.debugger, [ - self.debugger_msgs.new_request( - CMD_GET_EXCEPTION_DETAILS, - str(thread.id)), - ]) - - def test_suspend(self): - with self.launched(): - with self.hidden(): - _, thread = self.set_thread('x') - tid = self.send_event(thread.id, CMD_THREAD_SUSPEND) - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_event( - reason='pause', - threadId=tid, - text=None, - description=None, - preserveFocusHint=True, - ), - ]) - self.assert_received(self.debugger, []) - - def test_unknown_reason(self): - with self.launched(): - with self.hidden(): - _, thread = self.set_thread('x') - tid = self.send_event(thread.id, 99999) - received = self.vsc.received - - # TODO: Should this fail instead? - self.assert_vsc_received(received, [ - self.expected_event( - reason='pause', - threadId=tid, - text=None, - description=None, - preserveFocusHint=True, - ), - ]) - self.assert_received(self.debugger, []) - - # TODO: verify behavior - @unittest.skip('poorly specified') - def test_no_reason(self): - with self.launched(): - with self.hidden(): - _, thread = self.set_thread('x') - tid = self.send_event(thread.id, 'x') - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_event( - reason='pause', - threadId=tid, - text=None, - description=None, - preserveFocusHint=True, - ), - ]) - self.assert_received(self.debugger, []) - - # TODO: verify behavior - @unittest.skip('poorly specified') - def test_str_reason(self): - with self.launched(): - with self.hidden(): - _, thread = self.set_thread('x') - tid = self.send_event(thread.id, '???') - received = self.vsc.received - - # TODO: Should this fail instead? - self.assert_vsc_received(received, [ - self.expected_event( - reason='pause', - threadId=tid, - text=None, - description=None, - preserveFocusHint=True, - ), - ]) - self.assert_received(self.debugger, []) - - -class ThreadRunEventTests(ThreadEventTest, unittest.TestCase): - - CMD = CMD_THREAD_RESUME_SINGLE_NOTIFICATION - EVENT = 'continued' - - def pydevd_payload(self, threadid, reason): - return '{ "thread_id": "%s" }' % threadid - - def test_basic(self): - with self.launched(): - with self.hidden(): - _, thread = self.pause('x', *[ - # (pfid, func, file, line) - (2, 'spam', 'abc.py', 10), - (5, 'eggs', 'xyz.py', 2), - ]) - tid = self.send_event(thread.id, '???') - received = self.vsc.received - - self.assert_vsc_received(received, [ - self.expected_event( - threadId=tid, - ), - ]) - self.assert_received(self.debugger, []) - - -class GetExceptionBreakpointEventTests(PyDevdEventTest, unittest.TestCase): - - CMD = CMD_GET_BREAKPOINT_EXCEPTION - EVENT = None - - def pydevd_payload(self, tid, exc_type, stacktrace): - return self.debugger_msgs.format_breakpoint_exception(tid, exc_type, stacktrace) - - def test_basic(self): - trace = [('abc.py', 10, 'spam', 'eggs'), ] # (file, line, func, obj) - with self.launched(): - self.send_event(10, 'RuntimeError', trace) - received = self.vsc.received - - # Note: We don't send any events to client. So this should be empty. - self.assert_vsc_received(received, []) - self.assert_received(self.debugger, []) - -class ShowConsoleEventTests(PyDevdEventTest, unittest.TestCase): - - CMD = CMD_SHOW_CONSOLE - EVENT = None - - def pydevd_payload(self, threadid, *frames): - reason = self.CMD - return self.debugger_msgs.format_frames(threadid, reason, *frames) - - def test_unsupported(self): - ptid = 10 - frame = (2, 'spam', 'abc.py', 10) # (pfid, func, file, line) - with self.launched(): - with self.assertRaises(UnsupportedPyDevdCommandError): - self.send_event(ptid, frame) - received = self.vsc.received - - self.assert_vsc_received(received, []) - self.assert_received(self.debugger, []) - - -class WriteToConsoleEventTests(PyDevdEventTest, unittest.TestCase): - - CMD = CMD_WRITE_TO_CONSOLE - EVENT = None - - def pydevd_payload(self, msg, stdout=True): - ctx = 1 if stdout else 2 - return ''.format(msg, ctx) - - @unittest.skip('supported now') # TODO: write test - def test_unsupported(self): - with self.launched(): - with self.assertRaises(UnsupportedPyDevdCommandError): - self.send_event('output') - received = self.vsc.received - - self.assert_vsc_received(received, []) - self.assert_received(self.debugger, []) - -class UnsupportedPyDevdEventTests(PyDevdEventTest, unittest.TestCase): - - CMD = 12345 - EVENT = None - - def pydevd_paylaod(self, msg): - return msg - - def test_unsupported(self): - with self.launched(): - with self.assertRaises(UnsupportedPyDevdCommandError): - self.send_event('unsupported') - received = self.vsc.received - - self.assert_vsc_received(received, []) - self.assert_received(self.debugger, []) diff --git a/tests/ptvsd/__init__.py b/tests/ptvsd/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_attach/test_with_handled_exceptions.py b/tests/resources/system_tests/test_attach/test_with_handled_exceptions.py deleted file mode 100644 index fb99eb94..00000000 --- a/tests/resources/system_tests/test_attach/test_with_handled_exceptions.py +++ /dev/null @@ -1,10 +0,0 @@ -import sys -import ptvsd -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) -ptvsd.wait_for_attach() - -try: - raise ArithmeticError('Hello') -except Exception: - pass -sys.stdout.write('end') diff --git a/tests/resources/system_tests/test_basic/test_args/launch_with_args.py b/tests/resources/system_tests/test_basic/test_args/launch_with_args.py deleted file mode 100644 index 7f13fcad..00000000 --- a/tests/resources/system_tests/test_basic/test_args/launch_with_args.py +++ /dev/null @@ -1,3 +0,0 @@ -import sys - -sys.stdout.write("{}, {}".format(len(sys.argv), sys.argv)) diff --git a/tests/resources/system_tests/test_basic/test_args/mypkg_launch1/__init__.py b/tests/resources/system_tests/test_basic/test_args/mypkg_launch1/__init__.py deleted file mode 100644 index 7f13fcad..00000000 --- a/tests/resources/system_tests/test_basic/test_args/mypkg_launch1/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -import sys - -sys.stdout.write("{}, {}".format(len(sys.argv), sys.argv)) diff --git a/tests/resources/system_tests/test_basic/test_args/mypkg_launch1/__main__.py b/tests/resources/system_tests/test_basic/test_args/mypkg_launch1/__main__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_basic/test_output/attach_output.py b/tests/resources/system_tests/test_basic/test_output/attach_output.py deleted file mode 100644 index 432c3e79..00000000 --- a/tests/resources/system_tests/test_basic/test_output/attach_output.py +++ /dev/null @@ -1,7 +0,0 @@ -import sys -import ptvsd -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) -ptvsd.wait_for_attach() - -sys.stdout.write('yes') -sys.stderr.write('no') diff --git a/tests/resources/system_tests/test_basic/test_output/mypkg_attach1/__init__.py b/tests/resources/system_tests/test_basic/test_output/mypkg_attach1/__init__.py deleted file mode 100644 index 432c3e79..00000000 --- a/tests/resources/system_tests/test_basic/test_output/mypkg_attach1/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -import sys -import ptvsd -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) -ptvsd.wait_for_attach() - -sys.stdout.write('yes') -sys.stderr.write('no') diff --git a/tests/resources/system_tests/test_basic/test_output/mypkg_attach1/__main__.py b/tests/resources/system_tests/test_basic/test_output/mypkg_attach1/__main__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_basic/test_output/mypkg_launch1/__init__.py b/tests/resources/system_tests/test_basic/test_output/mypkg_launch1/__init__.py deleted file mode 100644 index c12ca96a..00000000 --- a/tests/resources/system_tests/test_basic/test_output/mypkg_launch1/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import sys - -sys.stdout.write('yes') -sys.stderr.write('no') diff --git a/tests/resources/system_tests/test_basic/test_output/mypkg_launch1/__main__.py b/tests/resources/system_tests/test_basic/test_output/mypkg_launch1/__main__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_basic/test_output/output.py b/tests/resources/system_tests/test_basic/test_output/output.py deleted file mode 100644 index c12ca96a..00000000 --- a/tests/resources/system_tests/test_basic/test_output/output.py +++ /dev/null @@ -1,4 +0,0 @@ -import sys - -sys.stdout.write('yes') -sys.stderr.write('no') diff --git a/tests/resources/system_tests/test_basic/test_without_output/attach_output.py b/tests/resources/system_tests/test_basic/test_without_output/attach_output.py deleted file mode 100644 index ebd7df75..00000000 --- a/tests/resources/system_tests/test_basic/test_without_output/attach_output.py +++ /dev/null @@ -1,4 +0,0 @@ -import sys -import ptvsd -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) -ptvsd.wait_for_attach() diff --git a/tests/resources/system_tests/test_basic/test_without_output/mypkg_attach1/__init__.py b/tests/resources/system_tests/test_basic/test_without_output/mypkg_attach1/__init__.py deleted file mode 100644 index ebd7df75..00000000 --- a/tests/resources/system_tests/test_basic/test_without_output/mypkg_attach1/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import sys -import ptvsd -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) -ptvsd.wait_for_attach() diff --git a/tests/resources/system_tests/test_basic/test_without_output/mypkg_attach1/__main__.py b/tests/resources/system_tests/test_basic/test_without_output/mypkg_attach1/__main__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_basic/test_without_output/mypkg_launch1/__init__.py b/tests/resources/system_tests/test_basic/test_without_output/mypkg_launch1/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_basic/test_without_output/mypkg_launch1/__main__.py b/tests/resources/system_tests/test_basic/test_without_output/mypkg_launch1/__main__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_basic/test_without_output/output.py b/tests/resources/system_tests/test_basic/test_without_output/output.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_break_into_debugger/attach_test.py b/tests/resources/system_tests/test_break_into_debugger/attach_test.py deleted file mode 100644 index e013ee91..00000000 --- a/tests/resources/system_tests/test_break_into_debugger/attach_test.py +++ /dev/null @@ -1,36 +0,0 @@ -import sys -import ptvsd -import os -import time - -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) - - -loopy = False -if os.getenv('PTVSD_WAIT_FOR_ATTACH', None) is not None: - print('waiting for attach') - ptvsd.wait_for_attach() -elif os.getenv('PTVSD_IS_ATTACHED', None) is not None: - print('checking is attached') - while not ptvsd.is_attached(): - time.sleep(0.1) -else: - loopy = True - - -def main(): - if loopy: - count = 0 - while count < 50: - print('one') - ptvsd.break_into_debugger() - time.sleep(0.1) - print('two') - count += 1 - else: - print('one') - ptvsd.break_into_debugger() - print('two') - - -main() diff --git a/tests/resources/system_tests/test_break_into_debugger/attach_test_breakpoint.py b/tests/resources/system_tests/test_break_into_debugger/attach_test_breakpoint.py deleted file mode 100644 index 2b1bf571..00000000 --- a/tests/resources/system_tests/test_break_into_debugger/attach_test_breakpoint.py +++ /dev/null @@ -1,36 +0,0 @@ -import sys -import ptvsd -import os -import time - -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) - - -loopy = False -if os.getenv('PTVSD_WAIT_FOR_ATTACH', None) is not None: - print('waiting for attach') - ptvsd.wait_for_attach() -elif os.getenv('PTVSD_IS_ATTACHED', None) is not None: - print('checking is attached') - while not ptvsd.is_attached(): - time.sleep(0.1) -else: - loopy = True - - -def main(): - if loopy: - count = 0 - while count < 50: - print('one') - breakpoint() # noqa - time.sleep(0.1) - print('two') - count += 1 - else: - print('one') - breakpoint() # noqa - print('two') - - -main() diff --git a/tests/resources/system_tests/test_break_into_debugger/launch_test.py b/tests/resources/system_tests/test_break_into_debugger/launch_test.py deleted file mode 100644 index 341c6dff..00000000 --- a/tests/resources/system_tests/test_break_into_debugger/launch_test.py +++ /dev/null @@ -1,10 +0,0 @@ -import ptvsd - - -def main(): - print('one') - ptvsd.break_into_debugger() - print('two') - - -main() diff --git a/tests/resources/system_tests/test_break_into_debugger/launch_test_breakpoint.py b/tests/resources/system_tests/test_break_into_debugger/launch_test_breakpoint.py deleted file mode 100644 index 90a3679d..00000000 --- a/tests/resources/system_tests/test_break_into_debugger/launch_test_breakpoint.py +++ /dev/null @@ -1,9 +0,0 @@ - - -def main(): - print('one') - breakpoint() # noqa - print('two') - - -main() diff --git a/tests/resources/system_tests/test_break_into_debugger/mypkg/__init__.py b/tests/resources/system_tests/test_break_into_debugger/mypkg/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_break_into_debugger/mypkg/__main__.py b/tests/resources/system_tests/test_break_into_debugger/mypkg/__main__.py deleted file mode 100644 index 341c6dff..00000000 --- a/tests/resources/system_tests/test_break_into_debugger/mypkg/__main__.py +++ /dev/null @@ -1,10 +0,0 @@ -import ptvsd - - -def main(): - print('one') - ptvsd.break_into_debugger() - print('two') - - -main() diff --git a/tests/resources/system_tests/test_break_into_debugger/mypkg_attach/__init__.py b/tests/resources/system_tests/test_break_into_debugger/mypkg_attach/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_break_into_debugger/mypkg_attach/__main__.py b/tests/resources/system_tests/test_break_into_debugger/mypkg_attach/__main__.py deleted file mode 100644 index e013ee91..00000000 --- a/tests/resources/system_tests/test_break_into_debugger/mypkg_attach/__main__.py +++ /dev/null @@ -1,36 +0,0 @@ -import sys -import ptvsd -import os -import time - -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) - - -loopy = False -if os.getenv('PTVSD_WAIT_FOR_ATTACH', None) is not None: - print('waiting for attach') - ptvsd.wait_for_attach() -elif os.getenv('PTVSD_IS_ATTACHED', None) is not None: - print('checking is attached') - while not ptvsd.is_attached(): - time.sleep(0.1) -else: - loopy = True - - -def main(): - if loopy: - count = 0 - while count < 50: - print('one') - ptvsd.break_into_debugger() - time.sleep(0.1) - print('two') - count += 1 - else: - print('one') - ptvsd.break_into_debugger() - print('two') - - -main() diff --git a/tests/resources/system_tests/test_break_into_debugger/mypkg_launch/__init__.py b/tests/resources/system_tests/test_break_into_debugger/mypkg_launch/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_break_into_debugger/mypkg_launch/__main__.py b/tests/resources/system_tests/test_break_into_debugger/mypkg_launch/__main__.py deleted file mode 100644 index 341c6dff..00000000 --- a/tests/resources/system_tests/test_break_into_debugger/mypkg_launch/__main__.py +++ /dev/null @@ -1,10 +0,0 @@ -import ptvsd - - -def main(): - print('one') - ptvsd.break_into_debugger() - print('two') - - -main() diff --git a/tests/resources/system_tests/test_break_into_debugger/mypkg_launch_breakpoint/__init__.py b/tests/resources/system_tests/test_break_into_debugger/mypkg_launch_breakpoint/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_break_into_debugger/mypkg_launch_breakpoint/__main__.py b/tests/resources/system_tests/test_break_into_debugger/mypkg_launch_breakpoint/__main__.py deleted file mode 100644 index 90a3679d..00000000 --- a/tests/resources/system_tests/test_break_into_debugger/mypkg_launch_breakpoint/__main__.py +++ /dev/null @@ -1,9 +0,0 @@ - - -def main(): - print('one') - breakpoint() # noqa - print('two') - - -main() diff --git a/tests/resources/system_tests/test_break_into_debugger/mypkg_reattach/__init__.py b/tests/resources/system_tests/test_break_into_debugger/mypkg_reattach/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_break_into_debugger/mypkg_reattach/__main__.py b/tests/resources/system_tests/test_break_into_debugger/mypkg_reattach/__main__.py deleted file mode 100644 index 088a6a29..00000000 --- a/tests/resources/system_tests/test_break_into_debugger/mypkg_reattach/__main__.py +++ /dev/null @@ -1,28 +0,0 @@ -import sys -import ptvsd -import os -import time - -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) - - -def main(): - count = 0 - while count < 50: - if os.getenv('PTVSD_WAIT_FOR_ATTACH', None) is not None: - print('waiting for attach') - ptvsd.wait_for_attach() - elif os.getenv('PTVSD_IS_ATTACHED', None) is not None: - print('checking is attached') - while not ptvsd.is_attached(): - time.sleep(0.1) - else: - pass - print('one') - ptvsd.break_into_debugger() - time.sleep(0.5) - print('two') - count += 1 - - -main() diff --git a/tests/resources/system_tests/test_break_into_debugger/mypkg_reattach_breakpoint/__init__.py b/tests/resources/system_tests/test_break_into_debugger/mypkg_reattach_breakpoint/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_break_into_debugger/mypkg_reattach_breakpoint/__main__.py b/tests/resources/system_tests/test_break_into_debugger/mypkg_reattach_breakpoint/__main__.py deleted file mode 100644 index 9cb7efbc..00000000 --- a/tests/resources/system_tests/test_break_into_debugger/mypkg_reattach_breakpoint/__main__.py +++ /dev/null @@ -1,28 +0,0 @@ -import sys -import ptvsd -import os -import time - -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) - - -def main(): - count = 0 - while count < 50: - if os.getenv('PTVSD_WAIT_FOR_ATTACH', None) is not None: - print('waiting for attach') - ptvsd.wait_for_attach() - elif os.getenv('PTVSD_IS_ATTACHED', None) is not None: - print('checking is attached') - while not ptvsd.is_attached(): - time.sleep(0.1) - else: - pass - print('one') - breakpoint() # noqa - time.sleep(0.5) - print('two') - count += 1 - - -main() diff --git a/tests/resources/system_tests/test_break_into_debugger/reattach_test.py b/tests/resources/system_tests/test_break_into_debugger/reattach_test.py deleted file mode 100644 index 088a6a29..00000000 --- a/tests/resources/system_tests/test_break_into_debugger/reattach_test.py +++ /dev/null @@ -1,28 +0,0 @@ -import sys -import ptvsd -import os -import time - -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) - - -def main(): - count = 0 - while count < 50: - if os.getenv('PTVSD_WAIT_FOR_ATTACH', None) is not None: - print('waiting for attach') - ptvsd.wait_for_attach() - elif os.getenv('PTVSD_IS_ATTACHED', None) is not None: - print('checking is attached') - while not ptvsd.is_attached(): - time.sleep(0.1) - else: - pass - print('one') - ptvsd.break_into_debugger() - time.sleep(0.5) - print('two') - count += 1 - - -main() diff --git a/tests/resources/system_tests/test_break_into_debugger/reattach_test_breakpoint.py b/tests/resources/system_tests/test_break_into_debugger/reattach_test_breakpoint.py deleted file mode 100644 index 9cb7efbc..00000000 --- a/tests/resources/system_tests/test_break_into_debugger/reattach_test_breakpoint.py +++ /dev/null @@ -1,28 +0,0 @@ -import sys -import ptvsd -import os -import time - -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) - - -def main(): - count = 0 - while count < 50: - if os.getenv('PTVSD_WAIT_FOR_ATTACH', None) is not None: - print('waiting for attach') - ptvsd.wait_for_attach() - elif os.getenv('PTVSD_IS_ATTACHED', None) is not None: - print('checking is attached') - while not ptvsd.is_attached(): - time.sleep(0.1) - else: - pass - print('one') - breakpoint() # noqa - time.sleep(0.5) - print('two') - count += 1 - - -main() diff --git a/tests/resources/system_tests/test_breakpoints/attach_output.py b/tests/resources/system_tests/test_breakpoints/attach_output.py deleted file mode 100644 index 432c3e79..00000000 --- a/tests/resources/system_tests/test_breakpoints/attach_output.py +++ /dev/null @@ -1,7 +0,0 @@ -import sys -import ptvsd -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) -ptvsd.wait_for_attach() - -sys.stdout.write('yes') -sys.stderr.write('no') diff --git a/tests/resources/system_tests/test_breakpoints/bar.py b/tests/resources/system_tests/test_breakpoints/bar.py deleted file mode 100644 index 2f51458e..00000000 --- a/tests/resources/system_tests/test_breakpoints/bar.py +++ /dev/null @@ -1,3 +0,0 @@ -def do_bar(): - assert 1 == 1 - print('bye') diff --git a/tests/resources/system_tests/test_breakpoints/foo.py b/tests/resources/system_tests/test_breakpoints/foo.py deleted file mode 100644 index 6866c39c..00000000 --- a/tests/resources/system_tests/test_breakpoints/foo.py +++ /dev/null @@ -1,8 +0,0 @@ -import bar - - -def do_foo(): - bar.do_bar() - - -do_foo() diff --git a/tests/resources/system_tests/test_breakpoints/loopy.py b/tests/resources/system_tests/test_breakpoints/loopy.py deleted file mode 100644 index 45c2e538..00000000 --- a/tests/resources/system_tests/test_breakpoints/loopy.py +++ /dev/null @@ -1,4 +0,0 @@ -a = 1 -b = 2 -for i in range(10): - c = a diff --git a/tests/resources/system_tests/test_breakpoints/mypkg_attach1/__init__.py b/tests/resources/system_tests/test_breakpoints/mypkg_attach1/__init__.py deleted file mode 100644 index 432c3e79..00000000 --- a/tests/resources/system_tests/test_breakpoints/mypkg_attach1/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -import sys -import ptvsd -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) -ptvsd.wait_for_attach() - -sys.stdout.write('yes') -sys.stderr.write('no') diff --git a/tests/resources/system_tests/test_breakpoints/mypkg_attach1/__main__.py b/tests/resources/system_tests/test_breakpoints/mypkg_attach1/__main__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_breakpoints/mypkg_bar/__init__.py b/tests/resources/system_tests/test_breakpoints/mypkg_bar/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_breakpoints/mypkg_bar/__main__.py b/tests/resources/system_tests/test_breakpoints/mypkg_bar/__main__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_breakpoints/mypkg_bar/bar.py b/tests/resources/system_tests/test_breakpoints/mypkg_bar/bar.py deleted file mode 100644 index 2f51458e..00000000 --- a/tests/resources/system_tests/test_breakpoints/mypkg_bar/bar.py +++ /dev/null @@ -1,3 +0,0 @@ -def do_bar(): - assert 1 == 1 - print('bye') diff --git a/tests/resources/system_tests/test_breakpoints/mypkg_foo/__init__.py b/tests/resources/system_tests/test_breakpoints/mypkg_foo/__init__.py deleted file mode 100644 index ff77fe66..00000000 --- a/tests/resources/system_tests/test_breakpoints/mypkg_foo/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -import mypkg_bar.bar - - -def do_foo(): - mypkg_bar.bar.do_bar() - - -do_foo() diff --git a/tests/resources/system_tests/test_breakpoints/mypkg_foo/__main__.py b/tests/resources/system_tests/test_breakpoints/mypkg_foo/__main__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_breakpoints/mypkg_launch1/__init__.py b/tests/resources/system_tests/test_breakpoints/mypkg_launch1/__init__.py deleted file mode 100644 index c12ca96a..00000000 --- a/tests/resources/system_tests/test_breakpoints/mypkg_launch1/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import sys - -sys.stdout.write('yes') -sys.stderr.write('no') diff --git a/tests/resources/system_tests/test_breakpoints/mypkg_launch1/__main__.py b/tests/resources/system_tests/test_breakpoints/mypkg_launch1/__main__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_breakpoints/output.py b/tests/resources/system_tests/test_breakpoints/output.py deleted file mode 100644 index c12ca96a..00000000 --- a/tests/resources/system_tests/test_breakpoints/output.py +++ /dev/null @@ -1,4 +0,0 @@ -import sys - -sys.stdout.write('yes') -sys.stderr.write('no') diff --git a/tests/resources/system_tests/test_disconnect/disconnect_test.py b/tests/resources/system_tests/test_disconnect/disconnect_test.py deleted file mode 100644 index 2303db41..00000000 --- a/tests/resources/system_tests/test_disconnect/disconnect_test.py +++ /dev/null @@ -1,32 +0,0 @@ -import os -import sys -import time -import ptvsd - -if os.getenv('PTVSD_ENABLE_ATTACH', None) is not None: - ptvsd.enable_attach((sys.argv[1], sys.argv[2])) - -if os.getenv('PTVSD_WAIT_FOR_ATTACH', None) is not None: - print('waiting for attach') - ptvsd.wait_for_attach() -elif os.getenv('PTVSD_IS_ATTACHED', None) is not None: - print('checking is attached') - while not ptvsd.is_attached(): - time.sleep(0.1) - - -def main(): - count = 0 - while count < 50: - print(count) - time.sleep(0.3) - if os.getenv('PTVSD_BREAK_INTO_DEBUGGER', None) is not None: - ptvsd.break_into_debugger() - count += 1 - path = os.getenv('PTVSD_TARGET_FILE', None) - if path is not None: - with open(path, 'a') as f: - print('HERE :)', file=f) - - -main() diff --git a/tests/resources/system_tests/test_disconnect/mypkg/__init__.py b/tests/resources/system_tests/test_disconnect/mypkg/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_disconnect/mypkg/__main__.py b/tests/resources/system_tests/test_disconnect/mypkg/__main__.py deleted file mode 100644 index c8418fff..00000000 --- a/tests/resources/system_tests/test_disconnect/mypkg/__main__.py +++ /dev/null @@ -1,32 +0,0 @@ -import os -import sys -import time -import ptvsd - -if os.getenv('PTVSD_ENABLE_ATTACH', None) is not None: - ptvsd.enable_attach((sys.argv[1], sys.argv[2])) - -if os.getenv('PTVSD_WAIT_FOR_ATTACH', None) is not None: - print('waiting for attach') - ptvsd.wait_for_attach() -elif os.getenv('PTVSD_IS_ATTACHED', None) is not None: - print('checking is attached') - while not ptvsd.is_attached(): - time.sleep(0.1) - - -def main(): - count = 0 - while count < 50: - print(count) - time.sleep(0.1) - if os.getenv('PTVSD_BREAK_INTO_DEBUGGER', None) is not None: - ptvsd.break_into_debugger() - count += 1 - path = os.getenv('PTVSD_TARGET_FILE', None) - if path is not None: - with open(path, 'a') as f: - print('HERE :)', file=f) - - -main() diff --git a/tests/resources/system_tests/test_exceptions/handled_exceptions_attach.py b/tests/resources/system_tests/test_exceptions/handled_exceptions_attach.py deleted file mode 100644 index fb99eb94..00000000 --- a/tests/resources/system_tests/test_exceptions/handled_exceptions_attach.py +++ /dev/null @@ -1,10 +0,0 @@ -import sys -import ptvsd -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) -ptvsd.wait_for_attach() - -try: - raise ArithmeticError('Hello') -except Exception: - pass -sys.stdout.write('end') diff --git a/tests/resources/system_tests/test_exceptions/handled_exceptions_launch.py b/tests/resources/system_tests/test_exceptions/handled_exceptions_launch.py deleted file mode 100644 index cc69949a..00000000 --- a/tests/resources/system_tests/test_exceptions/handled_exceptions_launch.py +++ /dev/null @@ -1,7 +0,0 @@ -import sys - -try: - raise ArithmeticError('Hello') -except Exception: - pass -sys.stdout.write('end') diff --git a/tests/resources/system_tests/test_exceptions/mypkg_attach1/__init__.py b/tests/resources/system_tests/test_exceptions/mypkg_attach1/__init__.py deleted file mode 100644 index 368113b5..00000000 --- a/tests/resources/system_tests/test_exceptions/mypkg_attach1/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -import ptvsd -import sys -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) -ptvsd.wait_for_attach() - - -def main(): - try: - raise ArithmeticError('Hello') - except Exception: - pass - sys.stdout.write('end') - - -main() diff --git a/tests/resources/system_tests/test_exceptions/mypkg_attach1/__main__.py b/tests/resources/system_tests/test_exceptions/mypkg_attach1/__main__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_exceptions/mypkg_attach_unhandled/__init__.py b/tests/resources/system_tests/test_exceptions/mypkg_attach_unhandled/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_exceptions/mypkg_attach_unhandled/__main__.py b/tests/resources/system_tests/test_exceptions/mypkg_attach_unhandled/__main__.py deleted file mode 100644 index da01300d..00000000 --- a/tests/resources/system_tests/test_exceptions/mypkg_attach_unhandled/__main__.py +++ /dev/null @@ -1,12 +0,0 @@ -import ptvsd -import sys -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) -ptvsd.wait_for_attach() - - -def main(): - raise ArithmeticError('Hello') - sys.stdout.write('end') - - -main() diff --git a/tests/resources/system_tests/test_exceptions/mypkg_launch1/__init__.py b/tests/resources/system_tests/test_exceptions/mypkg_launch1/__init__.py deleted file mode 100644 index cc69949a..00000000 --- a/tests/resources/system_tests/test_exceptions/mypkg_launch1/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -import sys - -try: - raise ArithmeticError('Hello') -except Exception: - pass -sys.stdout.write('end') diff --git a/tests/resources/system_tests/test_exceptions/mypkg_launch1/__main__.py b/tests/resources/system_tests/test_exceptions/mypkg_launch1/__main__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_exceptions/mypkg_launch_unhandled/__init__.py b/tests/resources/system_tests/test_exceptions/mypkg_launch_unhandled/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_exceptions/mypkg_launch_unhandled/__main__.py b/tests/resources/system_tests/test_exceptions/mypkg_launch_unhandled/__main__.py deleted file mode 100644 index a75779b2..00000000 --- a/tests/resources/system_tests/test_exceptions/mypkg_launch_unhandled/__main__.py +++ /dev/null @@ -1,4 +0,0 @@ -import sys - -raise ArithmeticError('Hello') -sys.stdout.write('end') diff --git a/tests/resources/system_tests/test_exceptions/unhandled_exceptions_attach.py b/tests/resources/system_tests/test_exceptions/unhandled_exceptions_attach.py deleted file mode 100644 index 4382052f..00000000 --- a/tests/resources/system_tests/test_exceptions/unhandled_exceptions_attach.py +++ /dev/null @@ -1,11 +0,0 @@ -import sys -import ptvsd -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) -ptvsd.wait_for_attach() - -def do_something(): - raise ArithmeticError('Hello') - - -do_something() -sys.stdout.write('end') diff --git a/tests/resources/system_tests/test_exceptions/unhandled_exceptions_launch.py b/tests/resources/system_tests/test_exceptions/unhandled_exceptions_launch.py deleted file mode 100644 index 4234ac51..00000000 --- a/tests/resources/system_tests/test_exceptions/unhandled_exceptions_launch.py +++ /dev/null @@ -1,3 +0,0 @@ -print('one') -raise ArithmeticError('Hello') -print('two') diff --git a/tests/resources/system_tests/test_forever/attach_forever.py b/tests/resources/system_tests/test_forever/attach_forever.py deleted file mode 100644 index 9f3db6fb..00000000 --- a/tests/resources/system_tests/test_forever/attach_forever.py +++ /dev/null @@ -1,15 +0,0 @@ -import ptvsd -import sys -import time - - -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) -ptvsd.wait_for_attach() - - -i = 0 -while True: - time.sleep(0.1) - # - print(i) - i += 1 diff --git a/tests/resources/system_tests/test_forever/launch_forever.py b/tests/resources/system_tests/test_forever/launch_forever.py deleted file mode 100644 index 370c019f..00000000 --- a/tests/resources/system_tests/test_forever/launch_forever.py +++ /dev/null @@ -1,7 +0,0 @@ -import time - -i = 0 -while True: - time.sleep(0.1) - print(i) - i += 1 diff --git a/tests/resources/system_tests/test_misc/mymod/__init__.py b/tests/resources/system_tests/test_misc/mymod/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_misc/mymod/__main__.py b/tests/resources/system_tests/test_misc/mymod/__main__.py deleted file mode 100644 index 35133dfb..00000000 --- a/tests/resources/system_tests/test_misc/mymod/__main__.py +++ /dev/null @@ -1,3 +0,0 @@ -print('one') -print('two') -print('three') diff --git a/tests/resources/system_tests/test_misc/nooutput.py b/tests/resources/system_tests/test_misc/nooutput.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_misc/output_test.py b/tests/resources/system_tests/test_misc/output_test.py deleted file mode 100644 index 2afd065c..00000000 --- a/tests/resources/system_tests/test_misc/output_test.py +++ /dev/null @@ -1 +0,0 @@ -print('Hello\tworld') diff --git a/tests/resources/system_tests/test_misc/returnvalues.py b/tests/resources/system_tests/test_misc/returnvalues.py deleted file mode 100644 index fe8f27e2..00000000 --- a/tests/resources/system_tests/test_misc/returnvalues.py +++ /dev/null @@ -1,12 +0,0 @@ -class MyClass(object): - def do_something(self): - return 'did something' - - -def my_func(): - return 'did more things' - - -MyClass().do_something() -my_func() -print('done') diff --git a/tests/resources/system_tests/test_misc/single_thread.py b/tests/resources/system_tests/test_misc/single_thread.py deleted file mode 100644 index d4edea06..00000000 --- a/tests/resources/system_tests/test_misc/single_thread.py +++ /dev/null @@ -1,2 +0,0 @@ - -print('check here') diff --git a/tests/resources/system_tests/test_misc/stoponentry.py b/tests/resources/system_tests/test_misc/stoponentry.py deleted file mode 100644 index 35133dfb..00000000 --- a/tests/resources/system_tests/test_misc/stoponentry.py +++ /dev/null @@ -1,3 +0,0 @@ -print('one') -print('two') -print('three') diff --git a/tests/resources/system_tests/test_misc/three_threads.py b/tests/resources/system_tests/test_misc/three_threads.py deleted file mode 100644 index 445ac68d..00000000 --- a/tests/resources/system_tests/test_misc/three_threads.py +++ /dev/null @@ -1,23 +0,0 @@ -import threading -import time - - -stop = False - - -def worker(tid, offset): - i = 0 - global stop - while not stop: - time.sleep(0.01) - i += 1 - - -threads = [] -for i in [111, 222]: - thread = threading.Thread(target=worker, args=(i, len(threads))) - threads.append(thread) - thread.start() - -print('check here') -stop = True diff --git a/tests/resources/system_tests/test_terminate/mypkg_launch1/__init__.py b/tests/resources/system_tests/test_terminate/mypkg_launch1/__init__.py deleted file mode 100644 index 510011e4..00000000 --- a/tests/resources/system_tests/test_terminate/mypkg_launch1/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import time - -while True: - time.sleep(1) diff --git a/tests/resources/system_tests/test_terminate/mypkg_launch1/__main__.py b/tests/resources/system_tests/test_terminate/mypkg_launch1/__main__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_terminate/simple.py b/tests/resources/system_tests/test_terminate/simple.py deleted file mode 100644 index 510011e4..00000000 --- a/tests/resources/system_tests/test_terminate/simple.py +++ /dev/null @@ -1,4 +0,0 @@ -import time - -while True: - time.sleep(1) diff --git a/tests/resources/system_tests/test_variables/for_sorting.py b/tests/resources/system_tests/test_variables/for_sorting.py deleted file mode 100644 index 3726c16f..00000000 --- a/tests/resources/system_tests/test_variables/for_sorting.py +++ /dev/null @@ -1,16 +0,0 @@ -b_test = {"spam": "A", "eggs": "B", "abcd": "C"} -_b_test = 12 -__b_test = 13 -__b_test__ = 14 - -a_test = 1 -_a_test = 2 -__a_test = 3 -__a_test__ = 4 - -c_test = {1: "one", 2: "two", 10: "ten"} -_c_test = 22 -__c_test = 23 -__c_test__ = 24 - -d = 3 diff --git a/tests/resources/system_tests/test_variables/mypkg_attach1/__init__.py b/tests/resources/system_tests/test_variables/mypkg_attach1/__init__.py deleted file mode 100644 index 432c3e79..00000000 --- a/tests/resources/system_tests/test_variables/mypkg_attach1/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -import sys -import ptvsd -ptvsd.enable_attach((sys.argv[1], sys.argv[2])) -ptvsd.wait_for_attach() - -sys.stdout.write('yes') -sys.stderr.write('no') diff --git a/tests/resources/system_tests/test_variables/mypkg_attach1/__main__.py b/tests/resources/system_tests/test_variables/mypkg_attach1/__main__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_variables/mypkg_launch1/__init__.py b/tests/resources/system_tests/test_variables/mypkg_launch1/__init__.py deleted file mode 100644 index c12ca96a..00000000 --- a/tests/resources/system_tests/test_variables/mypkg_launch1/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import sys - -sys.stdout.write('yes') -sys.stderr.write('no') diff --git a/tests/resources/system_tests/test_variables/mypkg_launch1/__main__.py b/tests/resources/system_tests/test_variables/mypkg_launch1/__main__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/resources/system_tests/test_variables/simple.py b/tests/resources/system_tests/test_variables/simple.py deleted file mode 100644 index d50fbe8f..00000000 --- a/tests/resources/system_tests/test_variables/simple.py +++ /dev/null @@ -1,3 +0,0 @@ -a = 1 -b = {"one": 1, "two": 2} -c = 3 diff --git a/tests/resources/system_tests/test_wait_on_exit/waitonexit.py b/tests/resources/system_tests/test_wait_on_exit/waitonexit.py deleted file mode 100644 index 8faa0568..00000000 --- a/tests/resources/system_tests/test_wait_on_exit/waitonexit.py +++ /dev/null @@ -1,8 +0,0 @@ -import os -import sys - -normal_exit = os.getenv('PTVSD_NORMAL_EXIT', None) -exit_code = 0 if normal_exit is not None else 20 - -print('Ready') -sys.exit(exit_code) diff --git a/tests/resources/system_tests/test_web_frameworks/django/attach/app.py b/tests/resources/system_tests/test_web_frameworks/django/attach/app.py deleted file mode 100644 index f4142abd..00000000 --- a/tests/resources/system_tests/test_web_frameworks/django/attach/app.py +++ /dev/null @@ -1,95 +0,0 @@ -import os -import signal -import sys -from django.conf import settings -from django.urls import path -from django.core.management import execute_from_command_line -from django.http import HttpResponse -from django.template import loader -import ptvsd - - -ptvsd_host = os.getenv('PTVSD_HOST', 'localhost') -ptvsd_port = os.getenv('PTVSD_PORT', '9879') -ptvsd.enable_attach((ptvsd_host, ptvsd_port)) -ptvsd.wait_for_attach() - - -def sigint_handler(signal, frame): - import django.dispatch - djshutdown = django.dispatch.Signal() - djshutdown.send('system') - sys.exit(0) - - -signal.signal(signal.SIGINT, sigint_handler) - - -settings.configure( - DEBUG=True, - SECRET_KEY='B21034EB-A1A8-4DDD-90B4-C13B67BE2AE7', - ROOT_URLCONF=sys.modules[__name__], - TEMPLATES=[ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'APP_DIRS': True, - 'DIRS': [ - 'templates/' - ] - }, - ] -) - - -def home(request): - title = 'hello' - content = 'Django-Django-Test' - template = loader.get_template('hello.html') - context = { - 'title': title, - 'content': content, - } - return HttpResponse(template.render(context, request)) - - -def bad_route_handled(request): - try: - raise ArithmeticError('Hello') - except Exception: - pass - title = 'hello' - content = 'Django-Django-Test' - template = loader.get_template('hello.html') - context = { - 'title': title, - 'content': content, - } - return HttpResponse(template.render(context, request)) - - -def bad_route_unhandled(request): - raise ArithmeticError('Hello') - title = 'hello' - content = 'Django-Django-Test' - template = loader.get_template('hello.html') - context = { - 'title': title, - 'content': content, - } - return HttpResponse(template.render(context, request)) - - -def exit_app(request): - os.kill(os.getpid(), signal.SIGTERM) - return HttpResponse('Done') - - -urlpatterns = [ - path('', home, name='home'), - path('handled', bad_route_handled, name='bad_route_handled'), - path('unhandled', bad_route_unhandled, name='bad_route_unhandled'), - path('exit', exit_app, name='exit_app'), -] - -if __name__ == '__main__': - execute_from_command_line(sys.argv) diff --git a/tests/resources/system_tests/test_web_frameworks/django/attach/templates/hello.html b/tests/resources/system_tests/test_web_frameworks/django/attach/templates/hello.html deleted file mode 100644 index 3784dad6..00000000 --- a/tests/resources/system_tests/test_web_frameworks/django/attach/templates/hello.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - {{ title }} - - - {{ content }} - - \ No newline at end of file diff --git a/tests/resources/system_tests/test_web_frameworks/django/launch/app.py b/tests/resources/system_tests/test_web_frameworks/django/launch/app.py deleted file mode 100644 index 8297dd86..00000000 --- a/tests/resources/system_tests/test_web_frameworks/django/launch/app.py +++ /dev/null @@ -1,88 +0,0 @@ -import os -import signal -import sys -from django.conf import settings -from django.urls import path -from django.core.management import execute_from_command_line -from django.http import HttpResponse -from django.template import loader - - -def sigint_handler(signal, frame): - import django.dispatch - djshutdown = django.dispatch.Signal() - djshutdown.send('system') - sys.exit(0) - - -signal.signal(signal.SIGINT, sigint_handler) - - -settings.configure( - DEBUG=True, - SECRET_KEY='CD8FF4C1-7E6C-4E45-922D-C796271F2345', - ROOT_URLCONF=sys.modules[__name__], - TEMPLATES=[ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'APP_DIRS': True, - 'DIRS': [ - 'templates/' - ] - }, - ] -) - - -def home(request): - title = 'hello' - content = 'Django-Django-Test' - template = loader.get_template('hello.html') - context = { - 'title': title, - 'content': content, - } - return HttpResponse(template.render(context, request)) - - -def bad_route_handled(request): - try: - raise ArithmeticError('Hello') - except Exception: - pass - title = 'hello' - content = 'Django-Django-Test' - template = loader.get_template('hello.html') - context = { - 'title': title, - 'content': content, - } - return HttpResponse(template.render(context, request)) - - -def bad_route_unhandled(request): - raise ArithmeticError('Hello') - title = 'hello' - content = 'Django-Django-Test' - template = loader.get_template('hello.html') - context = { - 'title': title, - 'content': content, - } - return HttpResponse(template.render(context, request)) - - -def exit_app(request): - os.kill(os.getpid(), signal.SIGTERM) - return HttpResponse('Done') - - -urlpatterns = [ - path('', home, name='home'), - path('handled', bad_route_handled, name='bad_route_handled'), - path('unhandled', bad_route_unhandled, name='bad_route_unhandled'), - path('exit', exit_app, name='exit_app'), -] - -if __name__ == '__main__': - execute_from_command_line(sys.argv) diff --git a/tests/resources/system_tests/test_web_frameworks/django/launch/templates/hello.html b/tests/resources/system_tests/test_web_frameworks/django/launch/templates/hello.html deleted file mode 100644 index 3784dad6..00000000 --- a/tests/resources/system_tests/test_web_frameworks/django/launch/templates/hello.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - {{ title }} - - - {{ content }} - - \ No newline at end of file diff --git a/tests/resources/system_tests/test_web_frameworks/flask/attach/app.py b/tests/resources/system_tests/test_web_frameworks/flask/attach/app.py deleted file mode 100644 index 2ed05744..00000000 --- a/tests/resources/system_tests/test_web_frameworks/flask/attach/app.py +++ /dev/null @@ -1,56 +0,0 @@ -import os -import ptvsd -from flask import Flask -from flask import render_template - - -ptvsd_host = os.getenv('PTVSD_HOST', 'localhost') -ptvsd_port = os.getenv('PTVSD_PORT', '9879') -ptvsd.enable_attach((ptvsd_host, ptvsd_port)) -ptvsd.wait_for_attach() - - -app = Flask(__name__) - - -@app.route("/") -def home(): - content = 'Flask-Jinja-Test' - return render_template( - "hello.html", - title='Hello', - content=content - ) - - -@app.route("/handled") -def bad_route_handled(): - try: - raise ArithmeticError('Hello') - except Exception: - pass - return render_template( - "hello.html", - title='Hello', - content='Flask-Jinja-Test' - ) - - -@app.route("/unhandled") -def bad_route_unhandled(): - raise ArithmeticError('Hello') - return render_template( - "hello.html", - title='Hello', - content='Flask-Jinja-Test' - ) - - -@app.route("/exit") -def exit_app(): - from flask import request - func = request.environ.get('werkzeug.server.shutdown') - if func is None: - raise RuntimeError('No shutdown') - func() - return 'Done' diff --git a/tests/resources/system_tests/test_web_frameworks/flask/attach/templates/hello.html b/tests/resources/system_tests/test_web_frameworks/flask/attach/templates/hello.html deleted file mode 100644 index 3784dad6..00000000 --- a/tests/resources/system_tests/test_web_frameworks/flask/attach/templates/hello.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - {{ title }} - - - {{ content }} - - \ No newline at end of file diff --git a/tests/resources/system_tests/test_web_frameworks/flask/launch/app.py b/tests/resources/system_tests/test_web_frameworks/flask/launch/app.py deleted file mode 100644 index 52758964..00000000 --- a/tests/resources/system_tests/test_web_frameworks/flask/launch/app.py +++ /dev/null @@ -1,48 +0,0 @@ -from flask import Flask -from flask import render_template - - -app = Flask(__name__) - - -@app.route("/") -def home(): - content = 'Flask-Jinja-Test' - return render_template( - "hello.html", - title='Hello', - content=content - ) - - -@app.route("/handled") -def bad_route_handled(): - try: - raise ArithmeticError('Hello') - except Exception: - pass - return render_template( - "hello.html", - title='Hello', - content='Flask-Jinja-Test' - ) - - -@app.route("/unhandled") -def bad_route_unhandled(): - raise ArithmeticError('Hello') - return render_template( - "hello.html", - title='Hello', - content='Flask-Jinja-Test' - ) - - -@app.route("/exit") -def exit_app(): - from flask import request - func = request.environ.get('werkzeug.server.shutdown') - if func is None: - raise RuntimeError('No shutdown') - func() - return 'Done' diff --git a/tests/resources/system_tests/test_web_frameworks/flask/launch/templates/hello.html b/tests/resources/system_tests/test_web_frameworks/flask/launch/templates/hello.html deleted file mode 100644 index 3784dad6..00000000 --- a/tests/resources/system_tests/test_web_frameworks/flask/launch/templates/hello.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - {{ title }} - - - {{ content }} - - \ No newline at end of file diff --git a/tests/system_tests/__init__.py b/tests/system_tests/__init__.py deleted file mode 100644 index b97ac27b..00000000 --- a/tests/system_tests/__init__.py +++ /dev/null @@ -1,472 +0,0 @@ -import contextlib -import os -import ptvsd -import signal -import sys -import time -import traceback -import unittest - -from collections import namedtuple -from ptvsd.socket import Address -from tests.helpers.debugadapter import DebugAdapter, wait_for_port_to_free -from tests.helpers.debugclient import EasyDebugClient as DebugClient -from tests.helpers.message import assert_is_subset -from tests.helpers.script import find_line -from tests.helpers.threading import get_locked_and_waiter -from tests.helpers.workspace import Workspace, PathEntry -from tests.helpers.vsc import parse_message, VSCMessages, Response, Event - - -ROOT = os.path.dirname(os.path.dirname(ptvsd.__file__)) -PORT = 9879 -CONNECT_TIMEOUT = 5.0 -DELAY_WAITING_FOR_SOCKETS = 1.0 - -DebugInfo = namedtuple('DebugInfo', 'host port starttype argv filename modulename env cwd attachtype verbose') # noqa -DebugInfo.__new__.__defaults__ = ('localhost', PORT, 'launch', [], None, None, None, None, None, False) # noqa - - -Debugger = namedtuple('Debugger', 'session adapter') - - -class ANYType(object): - def __repr__(self): - return 'ANY' - - -ANY = ANYType() # noqa - - -def _match_value(value, expected, allowextra=True): - if expected is ANY: - return True - - if isinstance(expected, dict): # TODO: Support any mapping? - if not isinstance(value, dict): - return False - if not allowextra and sorted(value) != sorted(expected): - return False - for key, val in expected.items(): - if key not in value: - return False - if not _match_value(value[key], val): - return False - return True - elif isinstance(expected, str): # str is a special case of sequence. - if not isinstance(value, str): - return False - return value == expected - elif isinstance(expected, (list, tuple)): # TODO: Support any sequence? - if not isinstance(value, (list, tuple)): - return False - if not allowextra and len(value) < len(expected): - return False - for val, exp in zip(value, expected): - if not _match_value(val, exp): - return False - return True - else: - return value == expected - - -def _match_event(msg, event, **body): - if msg.type != 'event': - return False - if msg.event != event: - return False - return _match_value(msg.body, body) - - -def _get_version(received, actual=ptvsd.__version__): - version = actual - for msg in received: - if _match_event(msg, 'output', data={'version': ANY}): - if msg.body['data']['version'] != actual: - version = '0+unknown' - break - return version - - -def _find_events(received, event, **body): - for i, msg in enumerate(received): - if _match_event(msg, event, **body): - yield i, msg - - -def _strip_messages(received, match_msg): - msgs = iter(received) - for msg in msgs: - if match_msg(msg): - break - yield msg - stripped = 1 - for msg in msgs: - if match_msg(msg): - stripped += 1 - else: - yield msg._replace(seq=msg.seq - stripped) - - -def _strip_exit(received): - def match(msg): - if _match_event(msg, 'exited'): - return True - if _match_event(msg, 'terminated'): - return True - if _match_event(msg, 'thread', reason=u'exited'): - return True - return False - return _strip_messages(received, match) - - -def _strip_output_event(received, output): - matched = False - - def match(msg): - if matched: - return False - else: - return _match_event(msg, 'output', output=output) - return _strip_messages(received, match) - - -def _strip_newline_output_events(received): - def match(msg): - return _match_event(msg, 'output', output=u'\n') - return _strip_messages(received, match) - - -def _strip_pydevd_output(out): - # TODO: Leave relevant lines from before the marker? - pre, sep, out = out.partition( - 'pydev debugger: starting' + os.linesep + os.linesep) - return out if sep else pre - - -def lifecycle_handshake(session, command='launch', options=None, - breakpoints=None, excbreakpoints=None, - threads=False): - with session.wait_for_event('initialized', timeout=CONNECT_TIMEOUT): - req_initialize = session.send_request( - 'initialize', - adapterID='spam', - ) - req_initialize.wait() - req_command = session.send_request(command, **options or {}) - req_command.wait() - req_threads = session.send_request('threads') if threads else None - - reqs_bps, reqs_exc, req_done = _configure( - session, - breakpoints, - excbreakpoints, - ) - - return (req_initialize, req_command, req_done, - reqs_bps, reqs_exc, req_threads) - - -def _configure(session, breakpoints, excbreakpoints): - reqs_bps = [] - for req in breakpoints or (): - reqs_bps.append( - session.send_request('setBreakpoints', **req)) - - reqs_exc = [] - for req in excbreakpoints or (): - reqs_exc.append( - session.send_request('setExceptionBreakpoints', **req)) - - # All config requests must be done before sending "configurationDone". - for req in reqs_bps + reqs_exc: - req.wait() - req_done = session.send_request('configurationDone') - return reqs_bps, reqs_exc, req_done - - -def react_to_stopped(session, tid): - req_threads = session.send_request('threads') - req_threads.wait() - - req_stacktrace = session.send_request( - 'stackTrace', - threadId=tid, - ) - req_stacktrace.wait() - - return req_threads, req_stacktrace - - -class TestsBase(object): - - @property - def workspace(self): - try: - return self._workspace - except AttributeError: - self._workspace = Workspace() - self.addCleanup(self._workspace.cleanup) - return self._workspace - - @property - def pathentry(self): - try: - return self._pathentry - except AttributeError: - self._pathentry = PathEntry() - self.addCleanup(self._pathentry.cleanup) - self._pathentry.install() - return self._pathentry - - def enable_verbose(self): - DebugAdapter.VERBOSE = True - - def write_script(self, name, content): - return self.workspace.write_python_script(name, content=content) - - def write_debugger_script(self, filename, port, run_as): - cwd = os.getcwd() - kwargs = { - 'filename': filename, - 'port_num': port, - 'debug_id': None, - 'debug_options': None, - 'run_as': run_as, - } - return self.write_script('debugger.py', """ - import sys - sys.path.insert(0, {!r}) - from ptvsd.debugger import debug - debug( - {filename!r}, - {port_num!r}, - {debug_id!r}, - {debug_options!r}, - {run_as!r}, - ) - """.format(cwd, **kwargs)) - - -class LifecycleTestsBase(TestsBase, unittest.TestCase): - @contextlib.contextmanager - def start_debugging(self, debug_info): - addr = Address('localhost', debug_info.port) - cwd = debug_info.cwd - env = debug_info.env - wait_for_port_to_free(debug_info.port) - - def _kill_proc(pid): - """If debugger does not end gracefully, then kill proc and - wait for socket connections to die out. """ - try: - os.kill(pid, signal.SIGTERM) - except Exception: - pass - time.sleep(1) # wait for socket connections to die out. - - def _wrap_and_reraise(session, ex, exc_type, exc_value, exc_traceback): - """If we have connetion errors, then re-raised wrapped in - ConnectionTimeoutError. If using py3, then chain exceptions so - we do not loose the original exception, else try hack approach - for py27.""" - messages = [] - formatted_ex = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback)) # noqa - try: - messages = [str(msg) for msg in - _strip_newline_output_events(session.received)] - except Exception: - pass - - message = """ -Session Messages: ------------------ -{} - -Original Error: ---------------- -{}""".format(os.linesep.join(messages), formatted_ex) - - raise Exception(message) - - def _handle_exception(ex, adapter, session): - exc_type, exc_value, exc_traceback = sys.exc_info() - _kill_proc(adapter.pid) - _wrap_and_reraise(session, ex, exc_type, exc_value, exc_traceback) - - if debug_info.verbose: - DebugAdapter.VERBOSE = True - if debug_info.attachtype == 'import' and \ - debug_info.modulename is not None: - argv = debug_info.argv - with DebugAdapter.start_wrapper_module( - debug_info.modulename, - argv, - env=env, - cwd=cwd) as adapter: - with DebugClient() as editor: - time.sleep(DELAY_WAITING_FOR_SOCKETS) - session = editor.attach_socket(addr, adapter) - try: - yield Debugger(session=session, adapter=adapter) - adapter.wait() - except Exception as ex: - _handle_exception(ex, adapter, session) - elif debug_info.attachtype == 'import' and \ - debug_info.starttype == 'attach' and \ - debug_info.filename is not None: - argv = debug_info.argv - adapter = DebugAdapter.start_embedded( - addr, - debug_info.filename, - argv=argv, - env=env, - cwd=cwd, - ) - with adapter: - with DebugClient() as editor: - time.sleep(DELAY_WAITING_FOR_SOCKETS) - session = editor.attach_socket(addr, adapter) - try: - yield Debugger(session=session, adapter=adapter) - adapter.wait() - except Exception as ex: - _handle_exception(ex, adapter, session) - elif debug_info.starttype == 'attach': - if debug_info.modulename is None: - name = debug_info.filename - kind = 'script' - else: - name = debug_info.modulename - kind = 'module' - argv = debug_info.argv - adapter = DebugAdapter.start_for_attach( - addr, - name=name, - extra=argv, - kind=kind, - env=env, - cwd=cwd, - ) - with adapter: - with DebugClient() as editor: - time.sleep(DELAY_WAITING_FOR_SOCKETS) - session = editor.attach_socket(addr, adapter) - try: - yield Debugger(session=session, adapter=adapter) - adapter.wait() - except Exception as ex: - _handle_exception(ex, adapter, session) - else: - if debug_info.filename is None: - argv = ['-m', debug_info.modulename] + debug_info.argv - else: - argv = [debug_info.filename] + debug_info.argv - with DebugClient( - port=debug_info.port, - connecttimeout=CONNECT_TIMEOUT) as editor: - time.sleep(DELAY_WAITING_FOR_SOCKETS) - adapter, session = editor.host_local_debugger( - argv, cwd=cwd, env=env) - try: - yield Debugger(session=session, adapter=adapter) - adapter.wait() - except Exception as ex: - _handle_exception(ex, adapter, session) - - @property - def messages(self): - try: - return self._messages - except AttributeError: - self._messages = VSCMessages() - return self._messages - - def create_source_file(self, file_name, source): - return self.write_script(file_name, source) - - def find_line(self, filepath, label): - with open(filepath) as scriptfile: - script = scriptfile.read() - return find_line(script, label) - - def reset_seq(self, responses): - for i, msg in enumerate(responses): - responses[i] = msg._replace(seq=i) - - def find_events(self, responses, event, body_contents={}): - def is_subset(body): - try: - assert_is_subset(body, body_contents) - return True - except Exception: - return False - return list(resp - for resp in responses - if (isinstance(resp, Event) and resp.event == event and - is_subset(resp.body))) - - def find_responses(self, responses, command, condition=lambda x: True): - return list( - response for response in responses - if isinstance(response, Response) and - response.command == command and - condition(response.body)) - - def remove_messages(self, responses, messages): - for msg in messages: - responses.remove(msg) - - def new_response(self, *args, **kwargs): - return self.messages.new_response(*args, **kwargs) - - def new_event(self, *args, **kwargs): - return self.messages.new_event(*args, **kwargs) - - def _wait_for_started(self): - lock, wait = get_locked_and_waiter() - - # TODO: There's a race with the initial "output" event. - def handle_msg(msg): - if msg.type != 'event': - return False - if msg.event != 'output': - return False - lock.release() - return True - - handlers = [ - (handle_msg, "event 'output'"), - ] - return handlers, (lambda: wait(reason="event 'output'")) - - def assert_received(self, received, expected): - from tests.helpers.message import assert_messages_equal - received = [parse_message(msg) for msg in received] - expected = [parse_message(msg) for msg in expected] - assert_messages_equal(received, expected) - - def assert_contains(self, received, expected): - from tests.helpers.message import assert_contains_messages - received = [parse_message(msg) for msg in received] - expected = [parse_message(msg) for msg in expected] - assert_contains_messages(received, expected) - - def assert_message_is_subset(self, received, expected): - from tests.helpers.message import assert_is_subset - received = parse_message(received) - expected = parse_message(expected) - assert_is_subset(received, expected) - - def assert_is_subset(self, received, expected): - from tests.helpers.message import assert_is_subset - assert_is_subset(received, expected) - - def new_version_event(self, received): - version = _get_version(received) - return self.new_event( - 'output', - category='telemetry', - output='ptvsd', - data={'version': version}, - ) diff --git a/tests/system_tests/test_basic.py b/tests/system_tests/test_basic.py deleted file mode 100644 index e83d34e3..00000000 --- a/tests/system_tests/test_basic.py +++ /dev/null @@ -1,282 +0,0 @@ -import os -import os.path -import unittest - -from tests.helpers.debugsession import Awaitable -from tests.helpers.resource import TestResources -from . import ( - _strip_newline_output_events, lifecycle_handshake, - LifecycleTestsBase, DebugInfo, PORT, -) - - -TEST_FILES = TestResources.from_module(__name__) -WITH_OUTPUT = TEST_FILES.sub('test_output') -WITHOUT_OUTPUT = TEST_FILES.sub('test_without_output') -WITH_ARGS = TEST_FILES.sub('test_args') -TEST_TERMINATION_FILES = TestResources.from_module( - 'tests.system_tests.test_terminate') - - -class BasicTests(LifecycleTestsBase): - - def run_test_output(self, debug_info): - options = {'debugOptions': ['RedirectOutput']} - - with self.start_debugging(debug_info) as dbg: - lifecycle_handshake(dbg.session, debug_info.starttype, - options=options) - - received = list(_strip_newline_output_events(dbg.session.received)) - self.assert_contains(received, [ - self.new_event('output', category='stdout', output='yes'), - self.new_event('output', category='stderr', output='no'), - ]) - - def run_test_arguments(self, debug_info, expected_args): - options = {'debugOptions': ['RedirectOutput']} - - with self.start_debugging(debug_info) as dbg: - lifecycle_handshake(dbg.session, debug_info.starttype, - options=options) - - received = list(_strip_newline_output_events(dbg.session.received)) - expected_output = '{}, {}'.format(len(expected_args), expected_args) - self.assert_contains(received, [ - self.new_event( - 'output', category='stdout', output=expected_output), - ]) - - def run_test_termination(self, debug_info): - with self.start_debugging(debug_info) as dbg: - session = dbg.session - - exited = session.get_awaiter_for_event('exited') - terminated = session.get_awaiter_for_event('terminated') - - (_, req_launch, _, _, _, _ - ) = lifecycle_handshake(dbg.session, debug_info.starttype, - threads=True) - - Awaitable.wait_all(req_launch, - session.get_awaiter_for_event('thread')) - disconnect = session.send_request('disconnect') - - Awaitable.wait_all(exited, terminated, disconnect) - - def run_test_without_output(self, debug_info): - options = {'debugOptions': ['RedirectOutput']} - - with self.start_debugging(debug_info) as dbg: - lifecycle_handshake(dbg.session, debug_info.starttype, - options=options, - threads=True) - - received = list(_strip_newline_output_events(dbg.session.received)) - - out = self.find_events(received, 'output', {'category': 'stdout'}) - err = self.find_events(received, 'output', {'category': 'stderr'}) - self.assertEqual(len(out + err), 0) - - -class LaunchFileTests(BasicTests): - - def test_with_output(self): - filename = WITH_OUTPUT.resolve('output.py') - cwd = os.path.dirname(filename) - self.run_test_output(DebugInfo(filename=filename, cwd=cwd)) - - def test_arguments(self): - filename = WITH_ARGS.resolve('launch_with_args.py') - cwd = os.path.dirname(filename) - argv = ['arg1', 'arg2'] - self.run_test_arguments( - DebugInfo(filename=filename, cwd=cwd, argv=argv), - [filename] + argv, - ) - - @unittest.skip('Broken') - def test_termination(self): - filename = TEST_TERMINATION_FILES.resolve('simple.py') - cwd = os.path.dirname(filename) - self.run_test_termination( - DebugInfo(filename=filename, cwd=cwd), - ) - - def test_without_output(self): - filename = WITHOUT_OUTPUT.resolve('output.py') - cwd = os.path.dirname(filename) - self.run_test_without_output( - DebugInfo(filename=filename, cwd=cwd), - ) - - -class LaunchPackageTests(BasicTests): - - def test_with_output(self): - module_name = 'mypkg_launch1' - cwd = WITH_OUTPUT.root - env = WITH_OUTPUT.env_with_py_path() - self.run_test_output( - DebugInfo(modulename=module_name, env=env, cwd=cwd), - ) - - def test_without_output(self): - module_name = 'mypkg_launch1' - cwd = WITHOUT_OUTPUT.root - env = WITHOUT_OUTPUT.env_with_py_path() - self.run_test_without_output( - DebugInfo(modulename=module_name, env=env, cwd=cwd), - ) - - @unittest.skip('Broken') - def test_termination(self): - module_name = 'mypkg_launch1' - cwd = TEST_TERMINATION_FILES.root - env = TEST_TERMINATION_FILES.env_with_py_path() - self.run_test_output( - DebugInfo(modulename=module_name, env=env, cwd=cwd), - ) - self.run_test_termination( - DebugInfo(modulename=module_name, cwd=cwd), - ) - - @unittest.skip('Broken') - def test_arguments(self): - module_name = 'mypkg_launch1' - cwd = WITH_ARGS.root - env = WITH_ARGS.env_with_py_path() - argv = ['arg1', 'arg2'] - self.run_test_arguments( - DebugInfo(modulename=module_name, env=env, cwd=cwd, argv=argv), - ['-m'] + argv, - ) - - -class ServerAttachTests(BasicTests): - - def test_with_output(self): - filename = WITH_OUTPUT.resolve('output.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_output( - DebugInfo( - filename=filename, - cwd=cwd, - starttype='attach', - argv=argv, - ), - ) - - def test_without_output(self): - filename = WITHOUT_OUTPUT.resolve('output.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_without_output( - DebugInfo( - filename=filename, - cwd=cwd, - starttype='attach', - argv=argv, - ), - ) - - -class PTVSDAttachTests(BasicTests): - - def test_with_output(self): - filename = WITH_OUTPUT.resolve('attach_output.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_output( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - ), - ) - - def test_without_output(self): - filename = WITHOUT_OUTPUT.resolve('attach_output.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_without_output( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - ), - ) - - -class ServerAttachPackageTests(BasicTests): - - def test_with_output(self): - module_name = 'mypkg_launch1' - cwd = WITH_OUTPUT.root - env = WITH_OUTPUT.env_with_py_path() - argv = ['localhost', str(PORT)] - self.run_test_output( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - starttype='attach', - ), - ) - - def test_without_output(self): - module_name = 'mypkg_launch1' - cwd = WITHOUT_OUTPUT.root - env = WITHOUT_OUTPUT.env_with_py_path() - argv = ['localhost', str(PORT)] - self.run_test_without_output( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - starttype='attach', - ), - ) - - -class PTVSDAttachPackageTests(BasicTests): - - def test_with_output(self): - #self.enable_verbose() - module_name = 'mypkg_attach1' - cwd = WITH_OUTPUT.root - env = WITH_OUTPUT.env_with_py_path() - argv = ['localhost', str(PORT)] - self.run_test_output( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - attachtype='import', - starttype='attach', - ), - ) - - def test_without_output(self): - module_name = 'mypkg_attach1' - cwd = WITHOUT_OUTPUT.root - env = WITHOUT_OUTPUT.env_with_py_path() - argv = ['localhost', str(PORT)] - self.run_test_without_output( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - attachtype='import', - starttype='attach', - ), - ) diff --git a/tests/system_tests/test_break_into_debugger.py b/tests/system_tests/test_break_into_debugger.py deleted file mode 100644 index 7ab53373..00000000 --- a/tests/system_tests/test_break_into_debugger.py +++ /dev/null @@ -1,331 +0,0 @@ -import os -import os.path -import time - -from tests.helpers.resource import TestResources -from . import ( - _strip_newline_output_events, lifecycle_handshake, - LifecycleTestsBase, DebugInfo, PORT -) - -TEST_FILES = TestResources.from_module(__name__) - - -class BreakIntoDebuggerTests(LifecycleTestsBase): - def run_test_attach_or_launch(self, debug_info, end_loop=False): - options = {'debugOptions': ['RedirectOutput']} - with self.start_debugging(debug_info) as dbg: - session = dbg.session - stopped = session.get_awaiter_for_event('stopped') - (_, req_launch_attach, _, _, _, _) = lifecycle_handshake( - session, - debug_info.starttype, - options=options) - req_launch_attach.wait(timeout=3.0) - stopped.wait(timeout=3.0) - thread_id = stopped.event.body['threadId'] - if end_loop: - self.set_var_to_end_loop(session, thread_id) - - exited = session.get_awaiter_for_event('exited') - session.send_request('continue', threadId=thread_id) - exited.wait(timeout=5.0) - - received = list(_strip_newline_output_events(dbg.session.received)) - self.assert_contains(received, [ - self.new_event('output', category='stdout', output='one'), - self.new_event('output', category='stdout', output='two'), - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) - - def set_var_to_end_loop(self, session, thread_id): - # Set count > 1000 to end the loop - req_stacktrace = session.send_request( - 'stackTrace', - threadId=thread_id, - ) - req_stacktrace.wait() - frames = req_stacktrace.resp.body['stackFrames'] - frame_id = frames[0]['id'] - req_scopes = session.send_request( - 'scopes', - frameId=frame_id, - ) - req_scopes.wait() - scopes = req_scopes.resp.body['scopes'] - variables_reference = scopes[0]['variablesReference'] - req_setvar = session.send_request( - 'setVariable', - variablesReference=variables_reference, - name='count', - value='1000' - ) - req_setvar.wait() - - def run_test_reattach(self, debug_info): - options = {'debugOptions': ['RedirectOutput']} - with self.start_debugging(debug_info) as dbg: - session = dbg.session - stopped = session.get_awaiter_for_event('stopped') - (_, req_launch_attach, _, _, _, _) = lifecycle_handshake( - session, - debug_info.starttype, - options=options, - threads=True) - req_launch_attach.wait(timeout=3.0) - stopped.wait(timeout=3.0) - - thread_id = stopped.event.body['threadId'] - req_disconnect = session.send_request('disconnect', restart=False) - req_disconnect.wait() - - time.sleep(1) - (_, req_launch_attach, _, _, _, _) = lifecycle_handshake( - session, - debug_info.starttype, - options=options, - threads=True) - req_launch_attach.wait(timeout=3.0) - - self.set_var_to_end_loop(session, thread_id) - session.send_request('continue', threadId=thread_id).wait(timeout=3.0) - req_disconnect = session.send_request('disconnect', restart=False) - req_disconnect.wait() - - -class LaunchFileBreakIntoDebuggerTests(BreakIntoDebuggerTests): - def test_launch_and_break(self): - for filename in ('launch_test.py', 'launch_test_breakpoint.py'): - filename = TEST_FILES.resolve(filename) - cwd = os.path.dirname(filename) - debug_info = DebugInfo(filename=filename, cwd=cwd) - self.run_test_attach_or_launch(debug_info) - - -class LaunchModuleBreakIntoDebuggerTests(BreakIntoDebuggerTests): - def test_launch_and_break(self): - for module_name in ('mypkg_launch', 'mypkg_launch_breakpoint'): - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.parent.root - self.run_test_attach_or_launch( - DebugInfo(modulename=module_name, env=env, cwd=cwd)) - - -class ServerAttachBreakIntoDebuggerTests(BreakIntoDebuggerTests): - def test_attach_and_break(self): - filename = TEST_FILES.resolve('launch_test.py') - cwd = os.path.dirname(filename) - debug_info = DebugInfo( - filename=filename, - cwd=cwd, - starttype='attach', - ) - self.run_test_attach_or_launch(debug_info) - - -class ServerAttachModuleBreakIntoDebuggerTests(BreakIntoDebuggerTests): - def test_attach_and_break(self): - module_name = 'mypkg_launch' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - debug_info = DebugInfo( - modulename=module_name, - cwd=cwd, - env=env, - starttype='attach', - ) - self.run_test_attach_or_launch(debug_info) - - -class PTVSDAttachBreakIntoDebuggerTests(BreakIntoDebuggerTests): - def test_attach_enable_wait_and_break(self): - # Uses enable_attach followed by wait_for_attach - # before calling break_into_debugger - filename = TEST_FILES.resolve('attach_test.py') - cwd = os.path.dirname(filename) - debug_info = DebugInfo( - filename=filename, - cwd=cwd, - argv=['localhost', str(PORT)], - env={'PTVSD_WAIT_FOR_ATTACH': 'True'}, - starttype='attach', - attachtype='import', - ) - self.run_test_attach_or_launch(debug_info) - - def test_attach_enable_check_and_break(self): - # Uses enable_attach followed by a loop that checks if the - # debugger is attached before calling break_into_debugger - filename = TEST_FILES.resolve('attach_test.py') - cwd = os.path.dirname(filename) - debug_info = DebugInfo( - filename=filename, - cwd=cwd, - argv=['localhost', str(PORT)], - env={'PTVSD_IS_ATTACHED': 'True'}, - starttype='attach', - attachtype='import', - ) - self.run_test_attach_or_launch(debug_info) - - def test_attach_enable_and_break(self): - # Uses enable_attach followed by break_into_debugger - # not is_attached check or wait_for_debugger - filename = TEST_FILES.resolve('attach_test.py') - cwd = os.path.dirname(filename) - debug_info = DebugInfo( - filename=filename, - cwd=cwd, - argv=['localhost', str(PORT)], - starttype='attach', - attachtype='import', - ) - self.run_test_attach_or_launch(debug_info, end_loop=True) - - def test_reattach_enable_wait_and_break(self): - # Uses enable_attach followed by wait_for_attach - # before calling break_into_debugger - for filename in ('reattach_test.py', 'reattach_test_breakpoint.py'): - filename = TEST_FILES.resolve(filename) - cwd = os.path.dirname(filename) - debug_info = DebugInfo( - filename=filename, - cwd=cwd, - argv=['localhost', str(PORT)], - env={'PTVSD_WAIT_FOR_ATTACH': 'True'}, - starttype='attach', - attachtype='import', - ) - self.run_test_reattach(debug_info) - - def test_reattach_enable_check_and_break(self): - # Uses enable_attach followed by a loop that checks if the - # debugger is attached before calling break_into_debugger - filename = TEST_FILES.resolve('reattach_test.py') - cwd = os.path.dirname(filename) - debug_info = DebugInfo( - filename=filename, - cwd=cwd, - argv=['localhost', str(PORT)], - env={'PTVSD_IS_ATTACHED': 'True'}, - starttype='attach', - attachtype='import', - ) - self.run_test_reattach(debug_info) - - def test_reattach_enable_and_break(self): - # Uses enable_attach followed by break_into_debugger - # not is_attached check or wait_for_debugger - filename = TEST_FILES.resolve('reattach_test.py') - cwd = os.path.dirname(filename) - debug_info = DebugInfo( - filename=filename, - cwd=cwd, - argv=['localhost', str(PORT)], - starttype='attach', - attachtype='import', - ) - self.run_test_reattach(debug_info) - - -class PTVSDAttachModuleBreakIntoDebuggerTests(BreakIntoDebuggerTests): - def test_attach_enable_wait_and_break(self): - # Uses enable_attach followed by wait_for_attach - # before calling break_into_debugger - module_name = 'mypkg_attach' - env = TEST_FILES.env_with_py_path() - env['PTVSD_WAIT_FOR_ATTACH'] = 'True' - cwd = TEST_FILES.root - debug_info = DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=['localhost', str(PORT)], - starttype='attach', - attachtype='import', - ) - self.run_test_attach_or_launch(debug_info) - - def test_attach_enable_check_and_break(self): - # Uses enable_attach followed by a loop that checks if the - # debugger is attached before calling break_into_debugger - module_name = 'mypkg_attach' - env = TEST_FILES.env_with_py_path() - env['PTVSD_IS_ATTACHED'] = 'True' - cwd = TEST_FILES.root - debug_info = DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=['localhost', str(PORT)], - starttype='attach', - attachtype='import', - ) - self.run_test_attach_or_launch(debug_info) - - def test_attach_enable_and_break(self): - # Uses enable_attach followed by break_into_debugger - # not is_attached check or wait_for_debugger - module_name = 'mypkg_attach' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - debug_info = DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=['localhost', str(PORT)], - starttype='attach', - attachtype='import', - ) - self.run_test_attach_or_launch(debug_info, end_loop=True) - - def test_reattach_enable_wait_and_break(self): - # Uses enable_attach followed by wait_for_attach - # before calling break_into_debugger - module_name = 'mypkg_reattach' - env = TEST_FILES.env_with_py_path() - env['PTVSD_WAIT_FOR_ATTACH'] = 'True' - cwd = TEST_FILES.root - debug_info = DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=['localhost', str(PORT)], - starttype='attach', - attachtype='import', - ) - self.run_test_reattach(debug_info) - - def test_reattach_enable_check_and_break(self): - # Uses enable_attach followed by a loop that checks if the - # debugger is attached before calling break_into_debugger - for module_name in ('mypkg_reattach', 'mypkg_reattach_breakpoint'): - env = TEST_FILES.env_with_py_path() - env['PTVSD_IS_ATTACHED'] = 'True' - cwd = TEST_FILES.root - debug_info = DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=['localhost', str(PORT)], - starttype='attach', - attachtype='import', - ) - self.run_test_reattach(debug_info) - - def test_reattach_enable_and_break(self): - # Uses enable_attach followed by break_into_debugger - # not is_attached check or wait_for_debugger - module_name = 'mypkg_reattach' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - debug_info = DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=['localhost', str(PORT)], - starttype='attach', - attachtype='import', - ) - self.run_test_reattach(debug_info) diff --git a/tests/system_tests/test_breakpoints.py b/tests/system_tests/test_breakpoints.py deleted file mode 100644 index ce49563f..00000000 --- a/tests/system_tests/test_breakpoints.py +++ /dev/null @@ -1,548 +0,0 @@ -import os -import os.path -import unittest - -from tests.helpers.resource import TestResources -from . import ( - _strip_newline_output_events, lifecycle_handshake, - LifecycleTestsBase, DebugInfo, PORT, -) - - -TEST_FILES = TestResources.from_module(__name__) - - -class BreakpointTests(LifecycleTestsBase): - - def run_test_with_break_points(self, debug_info, bp_filename, bp_line): - pathMappings = [] - # Required to ensure sourceReference = 0 - if (debug_info.starttype == 'attach'): - pathMappings.append({ - 'localRoot': debug_info.cwd, - 'remoteRoot': debug_info.cwd - }) - options = { - 'debugOptions': ['RedirectOutput'], - 'pathMappings': pathMappings - } - breakpoints = [{ - 'source': { - 'path': bp_filename - }, - 'breakpoints': [{ - 'line': bp_line - }] - }] - - with self.start_debugging(debug_info) as dbg: - session = dbg.session - with session.wait_for_event('stopped') as result: - (_, req_launch_attach, _, _, _, _, - ) = lifecycle_handshake(session, debug_info.starttype, - options=options, - breakpoints=breakpoints) - req_launch_attach.wait() - event = result['msg'] - tid = event.body['threadId'] - - req_stacktrace = session.send_request( - 'stackTrace', - threadId=tid, - ) - req_stacktrace.wait() - stacktrace = req_stacktrace.resp.body - - session.send_request( - 'continue', - threadId=tid, - ) - - received = list(_strip_newline_output_events(session.received)) - - self.assertGreaterEqual(stacktrace['totalFrames'], 1) - self.assert_is_subset(stacktrace, { - # We get Python and PTVSD frames as well. - # 'totalFrames': 2, - 'stackFrames': [{ - 'id': 1, - 'name': '', - 'source': { - 'sourceReference': 0 - }, - 'line': bp_line, - 'column': 1, - }], - }) - - self.assert_contains(received, [ - self.new_event( - 'stopped', - reason='breakpoint', - threadId=tid, - text=None, - description=None, - preserveFocusHint=False, - ), - self.new_event('continued', threadId=tid), - self.new_event('output', category='stdout', output='yes'), - self.new_event('output', category='stderr', output='no'), - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) - - def run_test_with_break_points_across_files( - self, debug_info, first_file, second_file, second_file_line, - expected_stacktrace): - breakpoints = [{ - 'source': { - 'path': second_file - }, - 'breakpoints': [{ - 'line': second_file_line - }] - }] - - with self.start_debugging(debug_info) as dbg: - session = dbg.session - with session.wait_for_event('stopped') as result: - (_, req_launch_attach, _, _, _, _, - ) = lifecycle_handshake(session, debug_info.starttype, - breakpoints=breakpoints) - req_launch_attach.wait() - event = result['msg'] - tid = event.body['threadId'] - - req_stacktrace = session.send_request( - 'stackTrace', - threadId=tid, - ) - req_stacktrace.wait() - stacktrace = req_stacktrace.resp.body - - session.send_request('continue', threadId=tid) - - self.assert_is_subset(stacktrace, expected_stacktrace) - - def run_test_conditional_break_points(self, debug_info): - breakpoints = [{ - 'source': { - 'path': debug_info.filename - }, - 'breakpoints': [{ - 'line': 4, - 'condition': 'i == 2' - }], - 'lines': [4] - }] - - with self.start_debugging(debug_info) as dbg: - session = dbg.session - with session.wait_for_event('stopped') as result: - lifecycle_handshake(session, debug_info.starttype, - breakpoints=breakpoints) - event = result['msg'] - tid = event.body['threadId'] - - req_stacktrace = session.send_request( - 'stackTrace', - threadId=tid, - ) - req_stacktrace.wait() - frames = req_stacktrace.resp.body['stackFrames'] - frame_id = frames[0]['id'] - req_scopes = session.send_request( - 'scopes', - frameId=frame_id, - ) - req_scopes.wait() - scopes = req_scopes.resp.body['scopes'] - variables_reference = scopes[0]['variablesReference'] - req_variables = session.send_request( - 'variables', - variablesReference=variables_reference, - ) - req_variables.wait() - variables = req_variables.resp.body['variables'] - - session.send_request('continue', threadId=tid) - - self.assert_is_subset(variables, [{ - 'name': 'a', - 'type': 'int', - 'value': '1', - 'evaluateName': 'a' - }, { - 'name': 'b', - 'type': 'int', - 'value': '2', - 'evaluateName': 'b' - }, { - 'name': 'c', - 'type': 'int', - 'value': '1', - 'evaluateName': 'c' - }, { - 'name': 'i', - 'type': 'int', - 'value': '2', - 'evaluateName': 'i' - }]) - - def run_test_hit_conditional_break_points(self, debug_info, **kwargs): - breakpoints = [{ - 'source': { - 'path': debug_info.filename - }, - 'breakpoints': [{ - 'line': 4, - 'hitCondition': kwargs['hit_condition'] - }], - 'lines': [4] - }] - - i_values = [] - with self.start_debugging(debug_info) as dbg: - session = dbg.session - hits = kwargs['hits'] - count = 0 - while count < hits: - if count == 0: - with session.wait_for_event('stopped') as result: - lifecycle_handshake(session, debug_info.starttype, - breakpoints=breakpoints) - event = result['msg'] - tid = event.body['threadId'] - - req_stacktrace = session.send_request( - 'stackTrace', - threadId=tid, - ) - req_stacktrace.wait() - frames = req_stacktrace.resp.body['stackFrames'] - frame_id = frames[0]['id'] - req_scopes = session.send_request( - 'scopes', - frameId=frame_id, - ) - req_scopes.wait() - scopes = req_scopes.resp.body['scopes'] - variables_reference = scopes[0]['variablesReference'] - req_variables = session.send_request( - 'variables', - variablesReference=variables_reference, - ) - req_variables.wait() - variables = req_variables.resp.body['variables'] - i_value = list(int(v['value']) - for v in variables - if v['name'] == 'i') - i_values.append(i_value[0] if len(i_value) > 0 else None) - count = count + 1 - if count < hits: - with session.wait_for_event('stopped') as result: - session.send_request('continue', threadId=tid) - else: - session.send_request('continue', threadId=tid) - self.assertEqual(i_values, kwargs['expected']) - - def run_test_logpoints(self, debug_info): - options = {'debugOptions': ['RedirectOutput']} - breakpoints = [{ - 'source': { - 'path': debug_info.filename - }, - 'breakpoints': [{ - 'line': 4, - 'logMessage': 'Sum of a + i = {a + i}' - }], - 'lines': [4] - }] - - with self.start_debugging(debug_info) as dbg: - session = dbg.session - lifecycle_handshake(session, debug_info.starttype, - options=options, - breakpoints=breakpoints) - - received = list(_strip_newline_output_events(session.received)) - expected_events = [ - self.new_event( - 'output', - category='stdout', - output='Sum of a + i = {}{}'.format(i + 1, os.linesep)) - for i in range(5) - ] - - self.assert_contains(received, expected_events) - - -class LaunchFileTests(BreakpointTests): - - def test_with_break_points(self): - filename = TEST_FILES.resolve('output.py') - cwd = os.path.dirname(filename) - self.run_test_with_break_points( - DebugInfo(filename=filename, cwd=cwd), - filename, - bp_line=3, - ) - - def test_with_break_points_across_files(self): - first_file = TEST_FILES.resolve('foo.py') - second_file = TEST_FILES.resolve('bar.py') - cwd = os.path.dirname(first_file) - expected_stacktrace = { - 'stackFrames': [{ - 'name': 'do_bar', - 'source': { - 'path': second_file, - 'sourceReference': 0 - }, - 'line': 2, - 'column': 1 - }, { - 'name': 'do_foo', - 'source': { - 'path': first_file, - 'sourceReference': 0 - }, - 'line': 5, - 'column': 1 - }, { - 'id': 3, - 'name': '', - 'source': { - 'path': first_file, - 'sourceReference': 0 - }, - 'line': 8, - 'column': 1 - }], - } - self.run_test_with_break_points_across_files( - DebugInfo(filename=first_file, cwd=cwd), - first_file, - second_file, - 2, - expected_stacktrace, - ) - - def test_conditional_break_points(self): - filename = TEST_FILES.resolve('loopy.py') - cwd = os.path.dirname(filename) - self.run_test_conditional_break_points( - DebugInfo(filename=filename, cwd=cwd)) - - def test_logpoints(self): - filename = TEST_FILES.resolve('loopy.py') - cwd = os.path.dirname(filename) - self.run_test_logpoints( - DebugInfo(filename=filename, cwd=cwd)) - - def test_hit_conditional_break_points_equal(self): - filename = TEST_FILES.resolve('loopy.py') - cwd = os.path.dirname(filename) - self.run_test_hit_conditional_break_points( - DebugInfo(filename=filename, cwd=cwd), - hit_condition='== 5', - hits=1, - expected=[4], - ) - - def test_hit_conditional_break_points_equal2(self): - filename = TEST_FILES.resolve('loopy.py') - cwd = os.path.dirname(filename) - self.run_test_hit_conditional_break_points( - DebugInfo(filename=filename, cwd=cwd), - hit_condition='5', - hits=1, - expected=[4], - ) - - def test_hit_conditional_break_points_greater(self): - filename = TEST_FILES.resolve('loopy.py') - cwd = os.path.dirname(filename) - self.run_test_hit_conditional_break_points( - DebugInfo(filename=filename, cwd=cwd), - hit_condition='> 5', - hits=5, - expected=[5, 6, 7, 8, 9], - ) - - def test_hit_conditional_break_points_greater_or_equal(self): - filename = TEST_FILES.resolve('loopy.py') - cwd = os.path.dirname(filename) - self.run_test_hit_conditional_break_points( - DebugInfo(filename=filename, cwd=cwd), - hit_condition='>= 5', - hits=6, - expected=[4, 5, 6, 7, 8, 9], - ) - - def test_hit_conditional_break_points_lesser(self): - filename = TEST_FILES.resolve('loopy.py') - cwd = os.path.dirname(filename) - self.run_test_hit_conditional_break_points( - DebugInfo(filename=filename, cwd=cwd), - hit_condition='< 5', - hits=4, - expected=[0, 1, 2, 3], - ) - - def test_hit_conditional_break_points_lesser_or_equal(self): - filename = TEST_FILES.resolve('loopy.py') - cwd = os.path.dirname(filename) - self.run_test_hit_conditional_break_points( - DebugInfo(filename=filename, cwd=cwd), - hit_condition='<= 5', - hits=5, - expected=[0, 1, 2, 3, 4], - ) - - def test_hit_conditional_break_points_mod(self): - filename = TEST_FILES.resolve('loopy.py') - cwd = os.path.dirname(filename) - self.run_test_hit_conditional_break_points( - DebugInfo(filename=filename, cwd=cwd), - hit_condition='% 4', - hits=2, - expected=[3, 7], - ) - - -class LaunchPackageTests(BreakpointTests): - - def test_with_break_points(self): - module_name = 'mypkg_launch1' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - bp_filename = os.path.join(cwd, module_name, '__init__.py') - self.run_test_with_break_points( - DebugInfo(modulename=module_name, env=env, cwd=cwd), - bp_filename, - bp_line=3, - ) - - def test_with_break_points_across_files(self): - module_name = 'mypkg_foo' - first_file = TEST_FILES.resolve(module_name, '__init__.py') - second_file = TEST_FILES.resolve('mypkg_bar', 'bar.py') - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - expected_stacktrace = { - 'stackFrames': [{ - 'name': 'do_bar', - 'source': { - 'path': second_file, - 'sourceReference': 0 - }, - 'line': 2, - 'column': 1 - }, { - 'name': 'do_foo', - 'source': { - 'path': first_file, - 'sourceReference': 0 - }, - 'line': 5, - 'column': 1 - }, { - 'id': 3, - 'name': '', - 'source': { - 'path': first_file, - 'sourceReference': 0 - }, - 'line': 8, - 'column': 1 - }], - } - self.run_test_with_break_points_across_files( - DebugInfo(modulename=module_name, cwd=cwd, env=env), - first_file, - second_file, - 2, - expected_stacktrace, - ) - - -class ServerAttachTests(BreakpointTests): - - def test_with_break_points(self): - filename = TEST_FILES.resolve('output.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_with_break_points( - DebugInfo( - filename=filename, - cwd=cwd, - starttype='attach', - argv=argv, - ), - filename, - bp_line=3, - ) - - -class PTVSDAttachTests(BreakpointTests): - @unittest.skip('Broken') - def test_with_break_points(self): - filename = TEST_FILES.resolve('attach_output.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_with_break_points( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - ), - filename, - bp_line=6, - ) - - -class ServerAttachPackageTests(BreakpointTests): - - def test_with_break_points(self): - module_name = 'mypkg_launch1' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - argv = ['localhost', str(PORT)] - bp_filename = os.path.join(cwd, module_name, '__init__.py') - self.run_test_with_break_points( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - starttype='attach', - ), - bp_filename, - bp_line=3, - ) - - -@unittest.skip('Needs fixing') -class PTVSDAttachPackageTests(BreakpointTests): - - def test_with_break_points(self): - module_name = 'mypkg_attach1' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - argv = ['localhost', str(PORT)] - bp_filename = os.path.join(cwd, module_name, '__init__.py') - self.run_test_with_break_points( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - attachtype='import', - starttype='attach', - ), - bp_filename, - bp_line=6, - ) diff --git a/tests/system_tests/test_connection.py b/tests/system_tests/test_connection.py deleted file mode 100644 index b65a31ad..00000000 --- a/tests/system_tests/test_connection.py +++ /dev/null @@ -1,158 +0,0 @@ -from __future__ import print_function - -import contextlib -import os -import time -import sys -import unittest - -import ptvsd._util -from ptvsd.socket import create_client, close_socket -from tests.helpers.proc import Proc -from tests.helpers.workspace import Workspace - - -@contextlib.contextmanager -def _retrier(timeout=1, persec=10, max=None, verbose=False): - steps = int(timeout * persec) + 1 - delay = 1.0 / persec - - @contextlib.contextmanager - def attempt(num): - if verbose: - print('*', end='') - sys.stdout.flush() - yield - if verbose: - if num % persec == 0: - print() - elif (num * 2) % persec == 0: - print(' ', end='') - - def attempts(): - # The first attempt always happens. - num = 1 - with attempt(num): - yield num - for num in range(2, steps): - if max is not None and num > max: - raise RuntimeError('too many attempts (max {})'.format(max)) - time.sleep(delay) - with attempt(num): - yield num - else: - raise RuntimeError('timed out') - yield attempts() - if verbose: - print() - - -class RawConnectionTests(unittest.TestCase): - - VERBOSE = False - #VERBOSE = True - - def setUp(self): - super(RawConnectionTests, self).setUp() - self.workspace = Workspace() - self.addCleanup(self.workspace.cleanup) - - def _propagate_verbose(self): - if not self.VERBOSE: - return - - def unset(): - Proc.VERBOSE = False - ptvsd._util.DEBUG = False - self.addCleanup(unset) - Proc.VERBOSE = True - ptvsd._util.DEBUG = True - - def _wait_for_ready(self, rpipe): - if self.VERBOSE: - print('waiting for ready') - line = b'' - while True: - c = os.read(rpipe, 1) - line += c - if c == b'\n': - if self.VERBOSE: - print(line.decode('utf-8'), end='') - if b'getting session socket' in line: - break - line = b'' - - @unittest.skip('there is a race here under travis') - def test_repeated(self): - def debug(msg): - if not self.VERBOSE: - return - print(msg) - - def connect(addr, wait=None, closeonly=False): - sock = create_client() - try: - sock.settimeout(1) - sock.connect(addr) - debug('>connected') - if wait is not None: - debug('>waiting') - time.sleep(wait) - finally: - debug('>closing') - if closeonly: - sock.close() - else: - close_socket(sock) - filename = self.workspace.write('spam.py', content=""" - raise Exception('should never run') - """) - addr = ('localhost', 5678) - self._propagate_verbose() - rpipe, wpipe = os.pipe() - self.addCleanup(lambda: os.close(rpipe)) - self.addCleanup(lambda: os.close(wpipe)) - proc = Proc.start_python_module('ptvsd', [ - '--server', - '--wait', - '--host', 'localhost', - '--port', '5678', - '--file', filename, - ], env={ - 'PTVSD_DEBUG': '1', - 'PTVSD_SOCKET_TIMEOUT': '1', - }, stdout=wpipe) - with proc: - # Wait for the server to spin up. - debug('>a') - with _retrier(timeout=3, verbose=self.VERBOSE) as attempts: - for _ in attempts: - try: - connect(addr) - break - except Exception: - pass - self._wait_for_ready(rpipe) - debug('>b') - connect(addr) - self._wait_for_ready(rpipe) - # We should be able to handle more connections. - debug('>c') - connect(addr) - self._wait_for_ready(rpipe) - # Give ptvsd long enough to try sending something. - debug('>d') - connect(addr, wait=0.2) - self._wait_for_ready(rpipe) - debug('>e') - connect(addr) - self._wait_for_ready(rpipe) - debug('>f') - connect(addr, closeonly=True) - self._wait_for_ready(rpipe) - debug('>g') - connect(addr) - self._wait_for_ready(rpipe) - debug('>h') - connect(addr) - self._wait_for_ready(rpipe) diff --git a/tests/system_tests/test_disconnect.py b/tests/system_tests/test_disconnect.py deleted file mode 100644 index 09db8ac9..00000000 --- a/tests/system_tests/test_disconnect.py +++ /dev/null @@ -1,311 +0,0 @@ -import os -import os.path -import random - -from tests.helpers.resource import TestResources -from . import ( - lifecycle_handshake, - LifecycleTestsBase, DebugInfo, PORT -) - -TEST_FILES = TestResources.from_module(__name__) - - -class CheckFile(object): - def __init__(self, root): - self._root = root - - def __enter__(self): - self._path = self._get_check_file_name(self._root) - return self - - def __exit__(self, *args): - if os.path.exists(self._path): - os.remove(self._path) - - @property - def filepath(self): - return self._path - - def _get_check_file_name(self, root): - name = 'test_%d.txt' % random.randint(10000, 99999) - path = os.path.join(root, name) - if os.path.exists(path): - os.remove(path) - return path - - -class ContinueOnDisconnectTests(LifecycleTestsBase): - def _wait_for_output(self, session): - count = 0 - while count < 3: - events = self.find_events(session.received, 'output') - for e in events: - try: - # the test outputs a number when it reaches the - # right spot - int(e.body['output']) - return - except ValueError: - pass - count += 1 - try: - outevent = session.get_awaiter_for_event('output') - outevent.wait(timeout=3.0) - except Exception: - pass - - def run_test_attach_disconnect(self, debug_info, path_to_check, - pause=False): - options = {'debugOptions': ['RedirectOutput']} - with self.start_debugging(debug_info) as dbg: - session = dbg.session - stopped = session.get_awaiter_for_event('stopped') - (_, req_launch_attach, _, _, _, req_threads - ) = lifecycle_handshake( - session, debug_info.starttype, - options=options, threads=True) - req_launch_attach.wait(timeout=3.0) - req_threads.wait(timeout=2.0) - - # ensure we see a output - self._wait_for_output(session) - - if pause: - req_pause = session.send_request('pause', threadId=0) - req_pause.wait(timeout=3.0) - - stopped.wait(timeout=2.0) - thread_id = stopped.event.body['threadId'] - self._set_var_to_end_loop(session, thread_id) - - session.send_request('disconnect', restart=False) - - if debug_info.starttype == 'launch': - self.assertFalse(os.path.exists(path_to_check)) - else: - self.assertTrue(os.path.exists(path_to_check)) - with open(path_to_check, 'r') as f: - self.assertEqual('HERE :)\n', f.read()) - - def _set_var_to_end_loop(self, session, thread_id): - # Set count > 1000 to end the loop - req_stacktrace = session.send_request( - 'stackTrace', - threadId=thread_id, - ) - req_stacktrace.wait() - frames = req_stacktrace.resp.body['stackFrames'] - frame_id = frames[0]['id'] - req_scopes = session.send_request( - 'scopes', - frameId=frame_id, - ) - req_scopes.wait() - scopes = req_scopes.resp.body['scopes'] - variables_reference = scopes[0]['variablesReference'] - req_setvar = session.send_request( - 'setVariable', - variablesReference=variables_reference, - name='count', - value='1000' - ) - req_setvar.wait() - - -class LaunchFileDisconnectLifecycleTests(ContinueOnDisconnectTests): - def test_launch_pause_disconnect(self): - filename = TEST_FILES.resolve('disconnect_test.py') - cwd = os.path.dirname(filename) - - with CheckFile(cwd) as cf: - debug_info = DebugInfo( - filename=filename, - cwd=cwd, - env={ - 'PTVSD_TARGET_FILE': cf.filepath, - }, - ) - self.run_test_attach_disconnect( - debug_info, cf.filepath, pause=True) - - def test_launch_break_disconnect(self): - filename = TEST_FILES.resolve('disconnect_test.py') - cwd = os.path.dirname(filename) - - with CheckFile(cwd) as cf: - debug_info = DebugInfo( - filename=filename, - cwd=cwd, - env={ - 'PTVSD_TARGET_FILE': cf.filepath, - 'PTVSD_BREAK_INTO_DEBUGGER': 'True', - } - ) - self.run_test_attach_disconnect( - debug_info, cf.filepath) - - -class LaunchModuleDisconnectLifecycleTests(ContinueOnDisconnectTests): - def test_launch_pause_disconnect(self): - module_name = 'mypkg' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.parent.root - with CheckFile(cwd) as cf: - env['PTVSD_TARGET_FILE'] = cf.filepath - debug_info = DebugInfo( - modulename=module_name, - cwd=cwd, - env=env - ) - self.run_test_attach_disconnect( - debug_info, cf.filepath, pause=True) - - def test_launch_break_disconnect(self): - module_name = 'mypkg' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.parent.root - with CheckFile(cwd) as cf: - env['PTVSD_TARGET_FILE'] = cf.filepath - env['PTVSD_BREAK_INTO_DEBUGGER'] = 'True' - debug_info = DebugInfo( - modulename=module_name, - cwd=cwd, - env=env, - ) - self.run_test_attach_disconnect( - debug_info, cf.filepath) - - -class ServerAttachDisconnectLifecycleTests(ContinueOnDisconnectTests): - def run_test(self, env, pause=False): - filename = TEST_FILES.resolve('disconnect_test.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - with CheckFile(cwd) as cf: - env['PTVSD_TARGET_FILE'] = cf.filepath - debug_info = DebugInfo( - filename=filename, - cwd=cwd, - argv=argv, - env=env, - starttype='attach', - ) - self.run_test_attach_disconnect( - debug_info, cf.filepath, pause=pause) - - def test_attach_pause_disconnect(self): - env = {} - self.run_test(env, pause=True) - - def test_attach_break_disconnect(self): - env = { - 'PTVSD_BREAK_INTO_DEBUGGER': 'True', - # this case should NOT use PTVSD_ENABLE_ATTACH - } - self.run_test(env) - - def test_attach_check_disconnect(self): - env = { - 'PTVSD_BREAK_INTO_DEBUGGER': 'True', - # this case should NOT use PTVSD_ENABLE_ATTACH - 'PTVSD_IS_ATTACHED': 'True', - } - self.run_test(env) - - -class PTVSDAttachDisconnectLifecycleTests(ContinueOnDisconnectTests): - def run_test(self, env, pause=False): - filename = TEST_FILES.resolve('disconnect_test.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - with CheckFile(cwd) as cf: - env['PTVSD_TARGET_FILE'] = cf.filepath - debug_info = DebugInfo( - filename=filename, - cwd=cwd, - argv=argv, - env=env, - starttype='attach', - attachtype='import', - ) - self.run_test_attach_disconnect( - debug_info, cf.filepath, pause=pause) - - def test_enable_attach_pause_disconnect(self): - env = { - 'PTVSD_ENABLE_ATTACH': 'True', - } - self.run_test(env, pause=True) - - def test_enable_attach_break_disconnect(self): - env = { - 'PTVSD_ENABLE_ATTACH': 'True', - 'PTVSD_BREAK_INTO_DEBUGGER': 'True', - } - self.run_test(env) - - def test_enable_attach_wait_disconnect(self): - env = { - 'PTVSD_ENABLE_ATTACH': 'True', - 'PTVSD_WAIT_FOR_ATTACH': 'True', - 'PTVSD_BREAK_INTO_DEBUGGER': 'True', - } - self.run_test(env) - - def test_enable_attach_check_disconnect(self): - env = { - 'PTVSD_ENABLE_ATTACH': 'True', - 'PTVSD_IS_ATTACHED': 'True', - 'PTVSD_BREAK_INTO_DEBUGGER': 'True', - } - self.run_test(env) - - -class PTVSDModuleAttachDisconnectLifecycleTests(ContinueOnDisconnectTests): - def run_test(self, env, pause=False): - module_name = 'mypkg' - env.update(TEST_FILES.env_with_py_path()) - cwd = TEST_FILES.root - argv = ['localhost', str(PORT)] - with CheckFile(cwd) as cf: - env['PTVSD_TARGET_FILE'] = cf.filepath - debug_info = DebugInfo( - modulename=module_name, - cwd=cwd, - argv=argv, - env=env, - starttype='attach', - attachtype='import', - ) - self.run_test_attach_disconnect( - debug_info, cf.filepath, pause=pause) - - def test_enable_attach_pause_disconnect(self): - env = { - 'PTVSD_ENABLE_ATTACH': 'True', - } - self.run_test(env, pause=True) - - def test_enable_attach_break_disconnect(self): - env = { - 'PTVSD_ENABLE_ATTACH': 'True', - 'PTVSD_BREAK_INTO_DEBUGGER': 'True', - } - self.run_test(env) - - def test_enable_attach_wait_disconnect(self): - env = { - 'PTVSD_ENABLE_ATTACH': 'True', - 'PTVSD_WAIT_FOR_ATTACH': 'True', - 'PTVSD_BREAK_INTO_DEBUGGER': 'True', - } - self.run_test(env) - - def test_enable_attach_check_disconnect(self): - env = { - 'PTVSD_ENABLE_ATTACH': 'True', - 'PTVSD_IS_ATTACHED': 'True', - 'PTVSD_BREAK_INTO_DEBUGGER': 'True', - } - self.run_test(env) diff --git a/tests/system_tests/test_enable_attach.py b/tests/system_tests/test_enable_attach.py deleted file mode 100644 index 429aac9c..00000000 --- a/tests/system_tests/test_enable_attach.py +++ /dev/null @@ -1,149 +0,0 @@ -import unittest - -from ptvsd import attach_server -from ptvsd.socket import Address -from tests import PROJECT_ROOT -from tests.helpers.debugadapter import DebugAdapter -from tests.helpers.debugclient import EasyDebugClient as DebugClient -from tests.helpers.lock import LockTimeoutError -from tests.helpers.script import set_lock, set_release, find_line -from . import LifecycleTestsBase, PORT, lifecycle_handshake - - -class EnableAttachTests(LifecycleTestsBase, unittest.TestCase): - - def setUp(self): - super(EnableAttachTests, self).setUp() - self._orig_wait_timeout = attach_server.WAIT_TIMEOUT - attach_server.WAIT_TIMEOUT = 1.0 - - def tearDown(self): - super(EnableAttachTests, self).tearDown() - attach_server.WAIT_TIMEOUT = self._orig_wait_timeout - - @unittest.skip('Fix test #721') - def test_does_not_block(self): - addr = Address('localhost', PORT) - filename = self.write_script('spam.py', """ - import sys - sys.path.insert(0, {!r}) - import ptvsd - ptvsd.enable_attach({}, redirect_output=False) - # - """.format(PROJECT_ROOT, tuple(addr)), - ) - lockfile = self.workspace.lockfile() - _, wait = set_release(filename, lockfile, 'ready') - - #DebugAdapter.VERBOSE = True - adapter = DebugAdapter.start_embedded(addr, filename) - with adapter: - wait(timeout=3) - adapter.wait() - - def test_never_call_wait_for_attach(self): - addr = Address('localhost', PORT) - filename = self.write_script('spam.py', """ - import sys - import threading - import time - - sys.path.insert(0, {!r}) - import ptvsd - ptvsd.enable_attach({}, redirect_output=False) - # - print('== ready ==') - - # Allow tracing to be triggered. - def wait(): - # - pass - t = threading.Thread(target=wait) - t.start() - for _ in range(100): # 10 seconds - print('-----') - t.join(0.1) - if not t.is_alive(): - break - t.join() - - print('== starting ==') - # - print('== done ==') - """.format(PROJECT_ROOT, tuple(addr)), - ) - lockfile1 = self.workspace.lockfile('ready.lock') - _, wait = set_release(filename, lockfile1, 'ready') - lockfile2 = self.workspace.lockfile('wait.log') - done, script = set_lock(filename, lockfile2, 'wait') - - bp = find_line(script, 'bp') - breakpoints = [{ - 'source': {'path': filename}, - 'breakpoints': [ - {'line': bp}, - ], - }] - - #DebugAdapter.VERBOSE = True - #DebugClient.SESSION.VERBOSE = True - adapter = DebugAdapter.start_embedded( - addr, - filename, - srvtimeout=None, - ) - with adapter: - # Wait longer that WAIT_TIMEOUT, so that debugging isn't - # immediately enabled in the script's thread. - wait(timeout=3.0) - - with DebugClient() as editor: - session = editor.attach_socket(addr, adapter, timeout=1) - stopped = session.get_awaiter_for_event('stopped') - with session.wait_for_event('thread') as result: - lifecycle_handshake(session, 'attach', - breakpoints=breakpoints, - threads=True) - event = result['msg'] - tid = event.body['threadId'] - - stopped.wait(timeout=5.0) - done() - session.send_request('continue', threadId=tid) - - adapter.wait() - out = str(adapter.output) - - self.assertIn('== ready ==', out) - self.assertIn('== starting ==', out) - - def test_wait_for_attach(self): - addr = Address('localhost', PORT) - filename = self.write_script('spam.py', """ - import sys - sys.path.insert(0, {!r}) - import ptvsd - ptvsd.enable_attach({}, redirect_output=False) - - ptvsd.wait_for_attach() - # - # - """.format(PROJECT_ROOT, tuple(addr)), - ) - lockfile1 = self.workspace.lockfile() - _, wait = set_release(filename, lockfile1, 'ready') - lockfile2 = self.workspace.lockfile() - done, _ = set_lock(filename, lockfile2, 'wait') - - adapter = DebugAdapter.start_embedded(addr, filename) - with adapter: - with DebugClient() as editor: - session = editor.attach_socket(addr, adapter, timeout=1) - # Ensure that it really does wait. - with self.assertRaises(LockTimeoutError): - wait(timeout=0.5) - - lifecycle_handshake(session, 'attach') - wait(timeout=1) - done() - adapter.wait() diff --git a/tests/system_tests/test_exceptions.py b/tests/system_tests/test_exceptions.py deleted file mode 100644 index 6d1aad0b..00000000 --- a/tests/system_tests/test_exceptions.py +++ /dev/null @@ -1,722 +0,0 @@ -import os -import os.path -import unittest - -from tests.helpers.debugsession import Awaitable -from tests.helpers.resource import TestResources -from . import ( - _strip_newline_output_events, - lifecycle_handshake, - LifecycleTestsBase, - DebugInfo, - PORT, -) - -TEST_FILES = TestResources.from_module(__name__) - - -class ExceptionTests(LifecycleTestsBase): - def run_test_not_breaking_into_handled_exceptions(self, debug_info): - excbreakpoints = [{'filters': ['uncaught']}] - options = {'debugOptions': ['RedirectOutput']} - - with self.start_debugging(debug_info) as dbg: - lifecycle_handshake( - dbg.session, - debug_info.starttype, - excbreakpoints=excbreakpoints, - options=options) - - received = list(_strip_newline_output_events(dbg.session.received)) - self.assert_contains(received, [ - self.new_event('output', category='stdout', output='end'), - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) - - def run_test_not_breaking_into_unhandled_exceptions(self, debug_info): - excbreakpoints = [{'filters': []}] - options = {'debugOptions': ['RedirectOutput']} - - with self.start_debugging(debug_info) as dbg: - lifecycle_handshake( - dbg.session, - debug_info.starttype, - excbreakpoints=excbreakpoints, - options=options) - - received = list(_strip_newline_output_events(dbg.session.received)) - # TODO: Re-enable after fixing #685 - #self.assertEqual( - # len(self.find_events(received, 'output', {'category': 'stdout'})), - # 1) - std_errs = self.find_events(received, 'output', {'category': 'stderr'}) - self.assertGreaterEqual(len(std_errs), 1) - std_err_msg = ''.join([msg.body['output'] for msg in std_errs]) - self.assertIn('ArithmeticError: Hello', std_err_msg) - self.assert_contains(received, [ - # TODO: Re-enable after fixing #685 - # self.new_event('output', category='stdout', output='one'), - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) - - def run_test_breaking_into_handled_exceptions(self, debug_info, - expected_source_name): - excbreakpoints = [{'filters': ['raised']}] - options = {'debugOptions': ['RedirectOutput']} - - with self.start_debugging(debug_info) as dbg: - stopped = dbg.session.get_awaiter_for_event('stopped') - (_, req_launch_attach, _, _, _, _) = lifecycle_handshake( - dbg.session, - debug_info.starttype, - excbreakpoints=excbreakpoints, - options=options, - threads=True) - - Awaitable.wait_all(req_launch_attach, stopped) - self.assertEqual(stopped.event.body['text'], 'ArithmeticError') - self.assertEqual(stopped.event.body['description'], 'Hello') - - thread_id = stopped.event.body['threadId'] - req_exc_info = dbg.session.send_request( - 'exceptionInfo', - threadId=thread_id, - ) - req_exc_info.wait() - exc_info = req_exc_info.resp.body - - self.assert_is_subset( - exc_info, { - 'exceptionId': 'ArithmeticError', - 'breakMode': 'always', - 'details': { - 'typeName': 'ArithmeticError', - 'source': expected_source_name - } - }) - - continued = dbg.session.get_awaiter_for_event('continued') - dbg.session.send_request( - 'continue', - threadId=thread_id, - ).wait() - Awaitable.wait_all(continued) - - received = list(_strip_newline_output_events(dbg.session.received)) - self.assert_contains(received, [ - self.new_event('continued', threadId=thread_id), - self.new_event('output', category='stdout', output='end'), - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) - - def run_test_breaking_into_unhandled_exceptions(self, debug_info, - expected_source_name): - excbreakpoints = [{'filters': ['uncaught']}] - options = {'debugOptions': ['RedirectOutput']} - - with self.start_debugging(debug_info) as dbg: - stopped = dbg.session.get_awaiter_for_event('stopped') - (_, req_launch_attach, _, _, _, _) = lifecycle_handshake( - dbg.session, - debug_info.starttype, - excbreakpoints=excbreakpoints, - options=options, - threads=True) - - req_launch_attach.wait(timeout=3.0) - stopped.wait(timeout=3.0) - self.assertEqual(stopped.event.body['text'], 'ArithmeticError') - self.assertEqual(stopped.event.body['description'], 'Hello') - - thread_id = stopped.event.body['threadId'] - req_exc_info = dbg.session.send_request( - 'exceptionInfo', - threadId=thread_id, - ) - req_exc_info.wait() - exc_info = req_exc_info.resp.body - - self.assert_is_subset( - exc_info, { - 'exceptionId': 'ArithmeticError', - 'breakMode': 'unhandled', - 'details': { - 'typeName': 'ArithmeticError', - 'source': expected_source_name - } - }) - - continued = dbg.session.get_awaiter_for_event('continued') - dbg.session.send_request( - 'continue', - threadId=thread_id, - ).wait() - Awaitable.wait_all(continued) - - received = list(_strip_newline_output_events(dbg.session.received)) - # TODO: Re-enable after fixing #685 - #self.assertEqual( - # len(self.find_events(received, 'output', {'category': 'stdout'})), - # 1) - std_errs = self.find_events(received, 'output', {'category': 'stderr'}) - self.assertGreaterEqual(len(std_errs), 1) - std_err_msg = ''.join([msg.body['output'] for msg in std_errs]) - self.assertIn('ArithmeticError: Hello', std_err_msg) - self.assert_contains(received, [ - self.new_event('continued', threadId=thread_id), - # TODO: Re-enable after fixing #685 - # self.new_event('output', category='stdout', output='one'), - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) - - def run_test_breaking_into_raised_exceptions_only(self, debug_info, - expected_source_name): - # NOTE: for this case we will be using a unhandled exception. The - # behavior expected here is that it breaks once when the exception - # was raised but not during postmortem - excbreakpoints = [{'filters': ['raised']}] - options = {'debugOptions': ['RedirectOutput']} - - with self.start_debugging(debug_info) as dbg: - stopped = dbg.session.get_awaiter_for_event('stopped') - (_, req_launch_attach, _, _, _, _ - ) = lifecycle_handshake(dbg.session, debug_info.starttype, - excbreakpoints=excbreakpoints, - options=options) - - Awaitable.wait_all(req_launch_attach, stopped) - self.assertEqual(stopped.event.body['text'], 'ArithmeticError') - self.assertEqual(stopped.event.body['description'], 'Hello') - - thread_id = stopped.event.body['threadId'] - req_exc_info = dbg.session.send_request( - 'exceptionInfo', - threadId=thread_id) - req_exc_info.wait() - exc_info = req_exc_info.resp.body - - self.assert_is_subset(exc_info, { - 'exceptionId': 'ArithmeticError', - 'breakMode': 'always', - 'details': { - 'typeName': 'ArithmeticError', - 'source': expected_source_name - } - }) - - continued = dbg.session.get_awaiter_for_event('continued') - dbg.session.send_request( - 'continue', - threadId=thread_id, - ).wait() - Awaitable.wait_all(continued) - - received = list(_strip_newline_output_events(dbg.session.received)) - self.assert_contains(received, [ - self.new_event('continued', threadId=thread_id), - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) - - def run_test_breaking_into_raised_and_unhandled_exceptions( - self, debug_info, expected_source_name): - excbreakpoints = [{'filters': ['raised', 'uncaught']}] - options = {'debugOptions': ['RedirectOutput']} - - expected = { - 'exceptionId': 'ArithmeticError', - 'breakMode': 'always', - 'details': { - 'typeName': 'ArithmeticError', - 'source': expected_source_name - } - } - - with self.start_debugging(debug_info) as dbg: - stopped = dbg.session.get_awaiter_for_event('stopped') - (_, req_launch_attach, _, _, _, _ - ) = lifecycle_handshake(dbg.session, debug_info.starttype, - excbreakpoints=excbreakpoints, - options=options) - - Awaitable.wait_all(req_launch_attach, stopped) - self.assertEqual(stopped.event.body['text'], 'ArithmeticError') - self.assertEqual(stopped.event.body['description'], 'Hello') - - thread_id = stopped.event.body['threadId'] - req_exc_info = dbg.session.send_request( - 'exceptionInfo', - threadId=thread_id) - req_exc_info.wait() - self.assert_is_subset(req_exc_info.resp.body, expected) - - stopped2 = dbg.session.get_awaiter_for_event('stopped') - continued = dbg.session.get_awaiter_for_event('continued') - dbg.session.send_request( - 'continue', - threadId=thread_id, - ).wait() - Awaitable.wait_all(stopped2, continued) - - # Second hit on uncaught exception - self.assertEqual(stopped2.event.body['text'], 'ArithmeticError') - self.assertEqual(stopped2.event.body['description'], 'Hello') - - req_exc_info2 = dbg.session.send_request( - 'exceptionInfo', - threadId=thread_id, - ) - req_exc_info2.wait() - self.assert_is_subset(req_exc_info2.resp.body, expected) - - continued2 = dbg.session.get_awaiter_for_event('continued') - dbg.session.send_request( - 'continue', - threadId=thread_id, - ).wait() - Awaitable.wait_all(continued2) - - received = list(_strip_newline_output_events(dbg.session.received)) - self.assert_contains(received, [ - self.new_event('continued', threadId=thread_id), - self.new_event('continued', threadId=thread_id), # expect 2 events - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) - - -class LaunchFileTests(ExceptionTests): - def test_not_breaking_into_handled_exceptions(self): - filename = TEST_FILES.resolve('handled_exceptions_launch.py') - cwd = os.path.dirname(filename) - self.run_test_not_breaking_into_handled_exceptions( - DebugInfo(filename=filename, cwd=cwd)) - - def test_not_breaking_into_unhandled_exceptions(self): - filename = TEST_FILES.resolve('unhandled_exceptions_launch.py') - cwd = os.path.dirname(filename) - self.run_test_not_breaking_into_unhandled_exceptions( - DebugInfo(filename=filename, cwd=cwd)) - - def test_breaking_into_handled_exceptions(self): - filename = TEST_FILES.resolve('handled_exceptions_launch.py') - cwd = os.path.dirname(filename) - self.run_test_breaking_into_handled_exceptions( - DebugInfo(filename=filename, cwd=cwd), filename) - - def test_breaking_into_unhandled_exceptions(self): - filename = TEST_FILES.resolve('unhandled_exceptions_launch.py') - cwd = os.path.dirname(filename) - self.run_test_breaking_into_unhandled_exceptions( - DebugInfo(filename=filename, cwd=cwd), filename) - - def test_breaking_into_raised_exceptions_only(self): - filename = TEST_FILES.resolve('unhandled_exceptions_launch.py') - cwd = os.path.dirname(filename) - self.run_test_breaking_into_raised_exceptions_only( - DebugInfo(filename=filename, cwd=cwd), filename) - - def test_breaking_into_raised_and_unhandled_exceptions(self): - filename = TEST_FILES.resolve('unhandled_exceptions_launch.py') - cwd = os.path.dirname(filename) - self.run_test_breaking_into_raised_and_unhandled_exceptions( - DebugInfo(filename=filename, cwd=cwd), filename) - - -class LaunchModuleExceptionLifecycleTests(ExceptionTests): - def test_not_breaking_into_handled_exceptions(self): - module_name = 'mypkg_launch1' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.parent.root - self.run_test_not_breaking_into_handled_exceptions( - DebugInfo(modulename=module_name, env=env, cwd=cwd)) - - def test_not_breaking_into_unhandled_exceptions(self): - module_name = 'mypkg_launch_unhandled' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.parent.root - self.run_test_not_breaking_into_unhandled_exceptions( - DebugInfo(modulename=module_name, env=env, cwd=cwd)) - - def test_breaking_into_handled_exceptions(self): - module_name = 'mypkg_launch1' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.parent.root - self.run_test_breaking_into_handled_exceptions( - DebugInfo(modulename=module_name, env=env, cwd=cwd), - os.path.join(TEST_FILES.root, module_name, '__init__.py')) - - def test_breaking_into_unhandled_exceptions(self): - module_name = 'mypkg_launch_unhandled' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.parent.root - self.run_test_breaking_into_unhandled_exceptions( - DebugInfo(modulename=module_name, env=env, cwd=cwd), - os.path.join(TEST_FILES.root, module_name, '__main__.py')) - - def test_breaking_into_raised_exceptions_only(self): - module_name = 'mypkg_launch_unhandled' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.parent.root - self.run_test_breaking_into_raised_exceptions_only( - DebugInfo(modulename=module_name, env=env, cwd=cwd), - os.path.join(TEST_FILES.root, module_name, '__main__.py')) - - def test_breaking_into_raised_and_unhandled_exceptions(self): - module_name = 'mypkg_launch_unhandled' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.parent.root - self.run_test_breaking_into_raised_and_unhandled_exceptions( - DebugInfo(modulename=module_name, env=env, cwd=cwd), - os.path.join(TEST_FILES.root, module_name, '__main__.py')) - - -class ServerAttachExceptionLifecycleTests(ExceptionTests): - def test_not_breaking_into_handled_exceptions(self): - filename = TEST_FILES.resolve('handled_exceptions_launch.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_not_breaking_into_handled_exceptions( - DebugInfo( - filename=filename, - cwd=cwd, - starttype='attach', - argv=argv, - )) - - def test_not_breaking_into_unhandled_exceptions(self): - filename = TEST_FILES.resolve('unhandled_exceptions_launch.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_not_breaking_into_unhandled_exceptions( - DebugInfo( - filename=filename, - cwd=cwd, - starttype='attach', - argv=argv, - )) - - def test_breaking_into_handled_exceptions(self): - filename = TEST_FILES.resolve('handled_exceptions_launch.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_breaking_into_handled_exceptions( - DebugInfo( - filename=filename, - cwd=cwd, - starttype='attach', - argv=argv, - ), filename) - - def test_breaking_into_unhandled_exceptions(self): - filename = TEST_FILES.resolve('unhandled_exceptions_launch.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_breaking_into_unhandled_exceptions( - DebugInfo( - filename=filename, - cwd=cwd, - starttype='attach', - argv=argv, - ), filename) - - def test_breaking_into_raised_exceptions_only(self): - filename = TEST_FILES.resolve('unhandled_exceptions_launch.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_breaking_into_raised_exceptions_only( - DebugInfo( - filename=filename, - cwd=cwd, - starttype='attach', - argv=argv, - ), filename) - - def test_breaking_into_raised_and_unhandled_exceptions(self): - filename = TEST_FILES.resolve('unhandled_exceptions_launch.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_breaking_into_raised_and_unhandled_exceptions( - DebugInfo( - filename=filename, - cwd=cwd, - starttype='attach', - argv=argv, - ), filename) - - -class PTVSDAttachExceptionLifecycleTests(ExceptionTests): - @unittest.skip('Needs fixing in #609, #580') - def test_not_breaking_into_handled_exceptions(self): - filename = TEST_FILES.resolve('handled_exceptions_attach.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_not_breaking_into_handled_exceptions( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - )) - - @unittest.skip('Needs fixing in #609, #580') - def test_not_breaking_into_unhandled_exceptions(self): - filename = TEST_FILES.resolve('unhandled_exceptions_attach.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_not_breaking_into_unhandled_exceptions( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - )) - - @unittest.skip('#686') - def test_breaking_into_handled_exceptions(self): - filename = TEST_FILES.resolve('handled_exceptions_attach.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_breaking_into_handled_exceptions( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - ), filename) - - @unittest.skip('Broken') - def test_breaking_into_unhandled_exceptions(self): - filename = TEST_FILES.resolve('unhandled_exceptions_attach.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_breaking_into_unhandled_exceptions( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - ), filename) - - @unittest.skip('Needs fixing in #609') - def test_breaking_into_raised_exceptions_only(self): - filename = TEST_FILES.resolve('unhandled_exceptions_attach.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_breaking_into_raised_exceptions_only( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - ), filename) - - @unittest.skip('Needs fixing in #609') - def test_breaking_into_raised_and_unhandled_exceptions(self): - filename = TEST_FILES.resolve('unhandled_exceptions_attach.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_breaking_into_raised_and_unhandled_exceptions( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - ), filename) - - -class ServerAttachModuleExceptionLifecycleTests(ExceptionTests): - def test_not_breaking_into_handled_exceptions(self): - module_name = 'mypkg_launch1' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - argv = ['localhost', str(PORT)] - self.run_test_not_breaking_into_handled_exceptions( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - starttype='attach', - )) - - def test_not_breaking_into_unhandled_exceptions(self): - module_name = 'mypkg_launch_unhandled' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - argv = ['localhost', str(PORT)] - self.run_test_not_breaking_into_unhandled_exceptions( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - starttype='attach', - )) - - def test_breaking_into_handled_exceptions(self): - module_name = 'mypkg_launch1' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - argv = ['localhost', str(PORT)] - self.run_test_breaking_into_handled_exceptions( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - starttype='attach', - ), os.path.join(TEST_FILES.root, module_name, '__init__.py')) - - def test_breaking_into_unhandled_exceptions(self): - module_name = 'mypkg_launch_unhandled' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - argv = ['localhost', str(PORT)] - self.run_test_breaking_into_unhandled_exceptions( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - starttype='attach', - ), - os.path.join(TEST_FILES.root, module_name, '__main__.py')) - - def test_breaking_into_raised_exceptions_only(self): - module_name = 'mypkg_launch_unhandled' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - argv = ['localhost', str(PORT)] - self.run_test_breaking_into_raised_exceptions_only( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - starttype='attach', - ), - os.path.join(TEST_FILES.root, module_name, '__main__.py')) - - def test_breaking_into_raised_and_unhandled_exceptions(self): - module_name = 'mypkg_launch_unhandled' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - argv = ['localhost', str(PORT)] - self.run_test_breaking_into_raised_and_unhandled_exceptions( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - starttype='attach', - ), - os.path.join(TEST_FILES.root, module_name, '__main__.py')) - - -class PTVSDAttachModuleExceptionLifecycleTests(ExceptionTests): - def test_not_breaking_into_handled_exceptions(self): - module_name = 'mypkg_attach1' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - argv = ['localhost', str(PORT)] - self.run_test_not_breaking_into_handled_exceptions( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - attachtype='import', - starttype='attach', - )) - - def test_not_breaking_into_unhandled_exceptions(self): - module_name = 'mypkg_attach_unhandled' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - argv = ['localhost', str(PORT)] - self.run_test_not_breaking_into_unhandled_exceptions( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - attachtype='import', - starttype='attach', - )) - - def test_breaking_into_handled_exceptions(self): - module_name = 'mypkg_attach1' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - argv = ['localhost', str(PORT)] - self.run_test_breaking_into_handled_exceptions( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - attachtype='import', - starttype='attach', - ), os.path.join(TEST_FILES.root, module_name, '__init__.py')) - - @unittest.skip('Broken') - def test_breaking_into_unhandled_exceptions(self): - module_name = 'mypkg_attach_unhandled' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - argv = ['localhost', str(PORT)] - self.run_test_breaking_into_unhandled_exceptions( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - attachtype='import', - starttype='attach', - ), - os.path.join(TEST_FILES.root, module_name, '__main__.py')) - - @unittest.skip('To be fixed #724') - def test_breaking_into_raised_exceptions_only(self): - module_name = 'mypkg_attach_unhandled' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - argv = ['localhost', str(PORT)] - self.run_test_breaking_into_raised_exceptions_only( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - attachtype='import', - starttype='attach', - ), - os.path.join(TEST_FILES.root, module_name, '__main__.py')) - - @unittest.skip('To be fixed #723') - def test_breaking_into_raised_and_unhandled_exceptions(self): - module_name = 'mypkg_attach_unhandled' - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - argv = ['localhost', str(PORT)] - self.run_test_breaking_into_raised_and_unhandled_exceptions( - DebugInfo( - modulename=module_name, - env=env, - cwd=cwd, - argv=argv, - attachtype='import', - starttype='attach', - ), - os.path.join(TEST_FILES.root, module_name, '__main__.py')) diff --git a/tests/system_tests/test_main.py b/tests/system_tests/test_main.py deleted file mode 100644 index aa6e2428..00000000 --- a/tests/system_tests/test_main.py +++ /dev/null @@ -1,866 +0,0 @@ -import os -import os.path -from textwrap import dedent -import unittest - -import ptvsd -from ptvsd.socket import Address -from ptvsd.wrapper import INITIALIZE_RESPONSE -from tests.helpers.debugadapter import DebugAdapter -from tests.helpers.debugclient import EasyDebugClient as DebugClient -from tests.helpers.lock import LockTimeoutError -from tests.helpers.script import find_line, set_lock, set_release -from tests.helpers.debugsession import Awaitable - -from . import ( - _strip_newline_output_events, lifecycle_handshake, TestsBase, - LifecycleTestsBase, _strip_output_event, _strip_exit, _find_events, - PORT, react_to_stopped, -) - - -ROOT = os.path.dirname(os.path.dirname(ptvsd.__file__)) - - -class CLITests(TestsBase, unittest.TestCase): - - def test_script_args(self): - lockfile = self.workspace.lockfile() - donescript, lockwait = lockfile.wait_for_script() - filename = self.pathentry.write_module('spam', """ - import sys - print(sys.argv) - sys.stdout.flush() - - {} - import time - time.sleep(10000) - """.format(donescript.replace('\n', '\n '))) - with DebugClient() as editor: - adapter, session = editor.launch_script( - filename, - '--eggs', - ) - lifecycle_handshake(session, 'launch') - lockwait(timeout=2.0) - out = adapter.output.decode('utf-8') - - self.assertIn(u"[{!r}, '--eggs']".format(filename), - out.strip().splitlines()) - - def test_run_to_completion(self): - filename = self.pathentry.write_module('spam', """ - import sys - print('done') - sys.stdout.flush() - """) - with DebugClient() as editor: - adapter, session = editor.launch_script( - filename, - ) - lifecycle_handshake(session, 'launch') - adapter.wait() - out = adapter.output.decode('utf-8') - rc = adapter.exitcode - - self.assertIn('done', out.splitlines()) - self.assertEqual(rc, 0) - - def test_failure(self): - filename = self.pathentry.write_module('spam', """ - import sys - sys.exit(42) - """) - with DebugClient() as editor: - adapter, session = editor.launch_script( - filename, - ) - lifecycle_handshake(session, 'launch') - adapter.wait() - rc = adapter.exitcode - - self.assertEqual(rc, 42) - - -class DebugTests(TestsBase, unittest.TestCase): - - def test_script(self): - argv = [] - filename = self.write_script('spam.py', """ - import sys - print('done') - sys.stdout.flush() - """) - script = self.write_debugger_script(filename, PORT, run_as='script') - with DebugClient(port=PORT) as editor: - adapter, session = editor.host_local_debugger(argv, script) - lifecycle_handshake(session, 'launch') - adapter.wait() - out = adapter.output.decode('utf-8') - rc = adapter.exitcode - - self.assertIn('done', out.splitlines()) - self.assertEqual(rc, 0) - - # python -m ptvsd --server --port 1234 --file one.py - - -class LifecycleTests(LifecycleTestsBase): - - def test_launch_ptvsd_client(self): - argv = [] - lockfile = self.workspace.lockfile() - done, waitscript = lockfile.wait_in_script() - filename = self.write_script('spam.py', waitscript) - script = self.write_debugger_script(filename, PORT, run_as='script') - with DebugClient(port=PORT) as editor: - adapter, session = editor.host_local_debugger( - argv, - script, - ) - with session.wait_for_event('exited'): - with session.wait_for_event('thread'): - (req_initialize, req_launch, req_config, _, _, _ - ) = lifecycle_handshake(session, 'launch') - - done() - adapter.wait() - - # Skipping the "thread exited" and "terminated" messages which - # may appear randomly in the received list. - received = list(_strip_newline_output_events(session.received)) - self.assert_received(received[:7], [ - self.new_version_event(session.received), - self.new_response(req_initialize.req, **INITIALIZE_RESPONSE), - self.new_event('initialized'), - self.new_response(req_launch.req), - self.new_response(req_config.req), - self.new_event('process', **{ - 'isLocalProcess': True, - 'systemProcessId': adapter.pid, - 'startMethod': 'launch', - 'name': filename, - }), - self.new_event('thread', reason='started', threadId=1), - ]) - - def test_launch_ptvsd_server(self): - lockfile = self.workspace.lockfile() - done, waitscript = lockfile.wait_in_script() - filename = self.write_script('spam.py', waitscript) - with DebugClient() as editor: - adapter, session = editor.launch_script( - filename, - timeout=5.0, - ) - - with session.wait_for_event('thread'): - (req_initialize, req_launch, req_config, _, _, _ - ) = lifecycle_handshake(session, 'launch') - - done() - adapter.wait() - - received = list(_strip_newline_output_events(session.received)) - self.assert_received(received[:7], [ - self.new_version_event(session.received), - self.new_response(req_initialize.req, **INITIALIZE_RESPONSE), - self.new_event('initialized'), - self.new_response(req_launch.req), - self.new_response(req_config.req), - self.new_event('process', **{ - 'isLocalProcess': True, - 'systemProcessId': adapter.pid, - 'startMethod': 'launch', - 'name': filename, - }), - self.new_event('thread', reason='started', threadId=1), - #self.new_event('thread', reason='exited', threadId=1), - #self.new_event('exited', exitCode=0), - #self.new_event('terminated'), - ]) - - def test_attach_started_separately(self): - lockfile = self.workspace.lockfile() - done, waitscript = lockfile.wait_in_script() - filename = self.write_script('spam.py', waitscript) - addr = Address('localhost', 8888) - with DebugAdapter.start_for_attach(addr, filename) as adapter: - with DebugClient() as editor: - session = editor.attach_socket(addr, adapter) - - with session.wait_for_event('thread'): - (req_initialize, req_launch, req_config, _, _, _ - ) = lifecycle_handshake(session, 'attach') - - done() - adapter.wait() - - received = list(_strip_newline_output_events(session.received)) - self.assert_received(received[:7], [ - self.new_version_event(session.received), - self.new_response(req_initialize.req, **INITIALIZE_RESPONSE), - self.new_event('initialized'), - self.new_response(req_launch.req), - self.new_response(req_config.req), - self.new_event('process', **{ - 'isLocalProcess': True, - 'systemProcessId': adapter.pid, - 'startMethod': 'attach', - 'name': filename, - }), - self.new_event('thread', reason='started', threadId=1), - #self.new_event('thread', reason='exited', threadId=1), - #self.new_event('exited', exitCode=0), - #self.new_event('terminated'), - ]) - - def test_attach_embedded(self): - lockfile = self.workspace.lockfile() - done, waitscript = lockfile.wait_in_script() - addr = Address('localhost', 8888) - script = dedent(""" - from __future__ import print_function - import sys - sys.path.insert(0, {!r}) - import ptvsd - ptvsd.enable_attach({}, redirect_output={}) - ptvsd.wait_for_attach() - - print('success!', end='') - - %s - """).format(os.getcwd(), tuple(addr), True) - filename = self.write_script('spam.py', script % waitscript) - with DebugAdapter.start_embedded(addr, filename) as adapter: - with DebugClient() as editor: - session = editor.attach_socket(addr, adapter) - - (req_initialize, req_launch, req_config, _, _, _ - ) = lifecycle_handshake(session, 'attach') - Awaitable.wait_all(req_initialize, req_launch) - done() - adapter.wait() - - for i in range(10): - # It could take some additional time for the adapter - # to actually get the success output, so, wait for the - # expected condition in a busy loop. - out = adapter.output.decode('utf-8') - if 'success!' in out: - break - import time - time.sleep(.1) - - received = list(_strip_newline_output_events(session.received)) - self.assert_contains(received, [ - self.new_version_event(session.received), - self.new_response(req_initialize.req, **INITIALIZE_RESPONSE), - self.new_event('initialized'), - self.new_response(req_launch.req), - self.new_response(req_config.req), - self.new_event('process', **{ - 'isLocalProcess': True, - 'systemProcessId': adapter.pid, - 'startMethod': 'attach', - 'name': filename, - }), - self.new_event('output', output='success!', category='stdout'), - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) - self.assertIn('success!', out) - - @unittest.skip('Flaky test, see test_reattach* for alternate re-attach tests') - def test_reattach(self): - lockfile1 = self.workspace.lockfile() - done1, waitscript1 = lockfile1.wait_in_script(timeout=5) - lockfile2 = self.workspace.lockfile() - done2, waitscript2 = lockfile2.wait_in_script(timeout=5) - filename = self.write_script('spam.py', waitscript1 + waitscript2) - addr = Address('localhost', 8888) - #DebugAdapter.VERBOSE = True - with DebugAdapter.start_for_attach(addr, filename) as adapter: - with DebugClient() as editor: - # Attach initially. - session1 = editor.attach_socket(addr, adapter) - with session1.wait_for_event('thread'): - (req_initialize, req_attach, req_config, _, _, _ - ) = lifecycle_handshake(session1, 'attach') - req_attach.wait(timeout=5.0) - done1() - req_disconnect = session1.send_request('disconnect') - req_disconnect.wait() - editor.detach(adapter) - - # Re-attach - session2 = editor.attach_socket(addr, adapter) - (req_initialize2, req_attach2, req_config2, _, _, _ - ) = lifecycle_handshake(session2, 'attach') - req_attach2.wait(timeout=5.0) - done2() - - adapter.wait() - - received = list(_strip_newline_output_events(session1.received)) - - self.assert_contains(received, [ - self.new_version_event(session1.received), - self.new_response(req_initialize.req, **INITIALIZE_RESPONSE), - self.new_event('initialized'), - self.new_response(req_attach.req), - self.new_response(req_config.req), - self.new_event('process', **{ - 'isLocalProcess': True, - 'systemProcessId': adapter.pid, - 'startMethod': 'attach', - 'name': filename, - }), - self.new_event('thread', reason='started', threadId=1), - self.new_response(req_disconnect.req), - ]) - self.messages.reset_all() - received = list(_strip_newline_output_events(session2.received)) - - self.assert_contains(received, [ - self.new_version_event(session2.received), - self.new_response(req_initialize2.req, **INITIALIZE_RESPONSE), - self.new_event('initialized'), - self.new_response(req_attach2.req), - self.new_response(req_config2.req), - self.new_event('process', **{ - 'isLocalProcess': True, - 'systemProcessId': adapter.pid, - 'startMethod': 'attach', - 'name': filename, - }), - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) - - def test_detach_clear_and_resume(self): - addr = Address('localhost', 8888) - filename = self.write_script('spam.py', """ - import sys - sys.path.insert(0, {!r}) - import ptvsd - - addr = {} - ptvsd.enable_attach(addr) - ptvsd.wait_for_attach() - - # - print('==before==') - - # - print('==after==') - - # - """.format(ROOT, tuple(addr))) - lockfile2 = self.workspace.lockfile() - done1, _ = set_lock(filename, lockfile2, 'before') - lockfile3 = self.workspace.lockfile() - _, wait2 = set_release(filename, lockfile3, 'done') - lockfile4 = self.workspace.lockfile() - done2, script = set_lock(filename, lockfile4, 'done') - - bp1 = find_line(script, 'before') - bp2 = find_line(script, 'after') - - #DebugAdapter.VERBOSE = True - adapter = DebugAdapter.start_embedded(addr, filename) - with adapter: - with DebugClient() as editor: - session1 = editor.attach_socket(addr, adapter, timeout=5) - with session1.wait_for_event('thread') as result: - with session1.wait_for_event('process'): - (req_init1, req_attach1, req_config1, - _, _, req_threads1, - ) = lifecycle_handshake(session1, 'attach', - threads=True) - event = result['msg'] - tid1 = event.body['threadId'] - - stopped_event = session1.get_awaiter_for_event('stopped') - req_bps = session1.send_request( - 'setBreakpoints', - source={'path': filename}, - breakpoints=[ - {'line': bp1}, - {'line': bp2}, - ], - ) - req_bps.wait() - - done1() - stopped_event.wait() - - req_threads2 = session1.send_request('threads') - req_stacktrace1 = session1.send_request( - 'stackTrace', - threadId=tid1, - ) - out1 = str(adapter.output) - - # Detach with execution stopped and 1 breakpoint left. - req_disconnect = session1.send_request('disconnect') - Awaitable.wait_all(req_threads2, req_stacktrace1, req_disconnect) # noqa - editor.detach(adapter) - try: - wait2() - except LockTimeoutError: - self.fail('execution never resumed upon detach ' - 'or breakpoints never cleared') - out2 = str(adapter.output) - import time - time.sleep(2) - session2 = editor.attach_socket(addr, adapter, timeout=5) - #session2.VERBOSE = True - with session2.wait_for_event('thread') as result: - with session2.wait_for_event('process'): - (req_init2, req_attach2, req_config2, - _, _, req_threads3, - ) = lifecycle_handshake(session2, 'attach', - threads=True) - event = result['msg'] - tid2 = event.body['threadId'] - - done2() - adapter.wait() - out3 = str(adapter.output) - - received = list(_strip_newline_output_events(session1.received)) - self.assert_contains(received, [ - self.new_version_event(session1.received), - self.new_response(req_init1.req, **INITIALIZE_RESPONSE), - self.new_event('initialized'), - self.new_response(req_attach1.req), - self.new_event( - 'thread', - threadId=tid1, - reason='started', - ), - self.new_response(req_threads1.req, **{ - 'threads': [{ - 'id': 1, - 'name': 'MainThread', - }], - }), - self.new_response(req_config1.req), - self.new_event('process', **{ - 'isLocalProcess': True, - 'systemProcessId': adapter.pid, - 'startMethod': 'attach', - 'name': filename, - }), - self.new_response(req_bps.req, **{ - 'breakpoints': [{ - 'id': 1, - 'line': bp1, - 'verified': True, - }, { - 'id': 2, - 'line': bp2, - 'verified': True, - }], - }), - self.new_event( - 'stopped', - threadId=tid1, - reason='breakpoint', - description=None, - text=None, - preserveFocusHint=False, - ), - self.new_response(req_threads2.req, **{ - 'threads': [{ - 'id': 1, - 'name': 'MainThread', - }], - }), - self.new_response(req_disconnect.req), - ]) - self.messages.reset_all() - received = list(_strip_newline_output_events(session2.received)) - # Sometimes the proc ends before the exited and terminated - # events are received. - received = list(_strip_exit(received)) - self.assert_contains(received, [ - self.new_version_event(session2.received), - self.new_response(req_init2.req, **INITIALIZE_RESPONSE), - self.new_event('initialized'), - self.new_response(req_attach2.req), - self.new_event( - 'thread', - threadId=tid2, - reason='started', - ), - self.new_response(req_threads3.req, **{ - 'threads': [{ - 'id': 1, - 'name': 'MainThread', - }], - }), - self.new_response(req_config2.req), - self.new_event('process', **{ - 'isLocalProcess': True, - 'systemProcessId': adapter.pid, - 'startMethod': 'attach', - 'name': filename, - }), - #self.new_event( - # 'thread', - # threadId=tid2, - # reason='exited', - #), - #self.new_event('exited', exitCode=0), - #self.new_event('terminated'), - ]) - # at breakpoint - self.assertEqual(out1, '') - # after detaching - self.assertIn('==before==', out2) - self.assertIn('==after==', out2) - # after reattach - self.assertEqual(out3, out2) - - @unittest.skip('not implemented') - def test_attach_exit_during_session(self): - # TODO: Ensure we see the 'terminated' and 'exited' events. - raise NotImplementedError - - @unittest.skip('re-attach needs fixing') - def test_attach_unknown(self): - lockfile = self.workspace.lockfile() - done, waitscript = lockfile.wait_in_script() - filename = self.write_script('spam.py', waitscript) - with DebugClient() as editor: - # Launch and detach. - # TODO: This is not an ideal way to spin up a process - # to which we can attach. However, ptvsd has no such - # capabilitity at present and attaching without ptvsd - # running isn't an option currently. - adapter, session = editor.launch_script( - filename, - ) - - lifecycle_handshake(session, 'launch') - editor.detach() - - # Re-attach. - session = editor.attach() - (req_initialize, req_launch, req_config, _, _, _ - ) = lifecycle_handshake(session, 'attach') - - done() - adapter.wait() - - received = list(_strip_newline_output_events(session.received)) - self.assert_received(received, [ - self.new_version_event(session.received), - self.new_response(req_initialize.req, **INITIALIZE_RESPONSE), - self.new_event('initialized'), - self.new_response(req_launch.req), - self.new_response(req_config.req), - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) - - def test_attach_breakpoints(self): - # See https://github.com/Microsoft/ptvsd/issues/448. - addr = Address('localhost', 8888) - filename = self.write_script('spam.py', """ - import sys - sys.path.insert(0, {!r}) - import ptvsd - - addr = {} - ptvsd.enable_attach(addr) - print('== waiting for attach ==') - # - ptvsd.wait_for_attach() - # - print('== attached! ==') - # - print('== done waiting ==') - """.format(ROOT, tuple(addr))) - lockfile1 = self.workspace.lockfile() - done1, _ = set_lock(filename, lockfile1, 'waiting') - lockfile2 = self.workspace.lockfile() - done2, script = set_lock(filename, lockfile2, 'bp 2') - - bp1 = find_line(script, 'attached') - bp2 = find_line(script, 'bp 2') - breakpoints = [{ - 'source': {'path': filename}, - 'breakpoints': [ - {'line': bp1}, - {'line': bp2}, - ], - }] - - options = { - 'pathMappings': [ - { - 'localRoot': os.path.dirname(filename), - 'remoteRoot': os.path.dirname(filename) - }, - # This specific mapping is for Mac. - # For some reason temp paths on Mac get prefixed with - # `private` when returned from ptvsd. - { - 'localRoot': os.path.dirname(filename), - 'remoteRoot': '/private' + os.path.dirname(filename) - } - ] - } - - #DebugAdapter.VERBOSE = True - adapter = DebugAdapter.start_embedded(addr, filename) - with adapter: - with DebugClient() as editor: - session = editor.attach_socket(addr, adapter, timeout=5) - - with session.wait_for_event('thread') as result: - with session.wait_for_event('process'): - (req_init, req_attach, req_config, - reqs_bps, _, req_threads1, - ) = lifecycle_handshake(session, 'attach', - breakpoints=breakpoints, - options=options, - threads=True) - Awaitable.wait_all(req_init, req_attach, req_config) - req_bps, = reqs_bps # There should only be one. - event = result['msg'] - tid = event.body['threadId'] - - # Grab the initial output. - out1 = next(adapter.output) # "waiting for attach" - line = adapter.output.readline() - while line: - out1 += line - line = adapter.output.readline() - - with session.wait_for_event('stopped'): - # Tell the script to proceed (at "# "). - # This leads to the first breakpoint. - done1() - req_threads2, req_stacktrace1 = react_to_stopped(session, tid) - out2 = str(adapter.output) # "" - - # Tell the script to proceed (at "# "). This - # leads to the second breakpoint. At this point - # execution is still stopped at the first breakpoint. - done2() - with session.wait_for_event('stopped'): - with session.wait_for_event('continued'): - req_continue1 = session.send_request( - 'continue', - threadId=tid, - ) - req_continue1.wait() - req_threads3, req_stacktrace2 = react_to_stopped(session, tid) - out3 = str(adapter.output) # "attached!" - - with session.wait_for_event('continued'): - req_continue2 = session.send_request( - 'continue', - threadId=tid, - ) - req_continue2.wait() - - adapter.wait() - out4 = str(adapter.output) # "done waiting" - - # Output between enable_attach() and wait_for_attach() may - # be sent at a relatively arbitrary time (or not at all). - # So we ignore it by removing it from the message list. - received = list(_strip_output_event(session.received, - u'== waiting for attach ==')) - received = list(_strip_newline_output_events(received)) - # There's an ordering race with continue/continued that pops - # up occasionally. We work around that by manually fixing the - # order. - for pos, msg in _find_events(received, 'continued'): - prev = received[pos-1] - if prev.type != 'response' or prev.command != 'continue': - received.pop(pos-1) - received.insert(pos + 1, prev) - # Sometimes the proc ends before the exited and terminated - # events are received. - received = list(_strip_exit(received)) - self.assert_contains(received, [ - self.new_version_event(session.received), - self.new_response(req_init.req, **INITIALIZE_RESPONSE), - self.new_event('initialized'), - self.new_response(req_attach.req), - self.new_event( - 'thread', - threadId=tid, - reason='started', - ), - self.new_response(req_threads1.req, **{ - 'threads': [{ - 'id': 1, - 'name': 'MainThread', - }], - }), - self.new_response(req_bps.req, **{ - 'breakpoints': [{ - 'id': 1, - 'line': bp1, - 'verified': True, - }, { - 'id': 2, - 'line': bp2, - 'verified': True, - }], - }), - self.new_response(req_config.req), - self.new_event('process', **{ - 'isLocalProcess': True, - 'systemProcessId': adapter.pid, - 'startMethod': 'attach', - 'name': filename, - }), - self.new_event( - 'stopped', - threadId=tid, - reason='breakpoint', - description=None, - text=None, - preserveFocusHint=False, - ), - self.new_response(req_threads2.req, **{ - 'threads': [{ - 'id': 1, - 'name': 'MainThread', - }], - }), - self.new_event( - 'module', - module={ - 'id': 1, - 'name': '__main__', - 'path': filename, - 'package': None, - }, - reason='new', - ), - self.new_response(req_stacktrace1.req, **{ - 'totalFrames': 1, - 'stackFrames': [{ - 'id': 1, - 'name': '', - 'source': { - 'path': filename, - 'sourceReference': 0, - }, - 'line': bp1, - 'column': 1, - }], - }), - self.new_response(req_continue1.req, **{ - 'allThreadsContinued': True - }), - self.new_event('continued', threadId=tid), - self.new_event( - 'output', - category='stdout', - output='== attached! ==', - ), - self.new_event( - 'stopped', - threadId=tid, - reason='breakpoint', - description=None, - text=None, - preserveFocusHint=False, - ), - self.new_response(req_threads3.req, **{ - 'threads': [{ - 'id': 1, - 'name': 'MainThread', - }], - }), - self.new_response(req_stacktrace2.req, **{ - 'totalFrames': 1, - 'stackFrames': [{ - 'id': 1, - 'name': '', - 'source': { - 'path': filename, - 'sourceReference': 0, - }, - 'line': bp2, - 'column': 1, - }], - }), - self.new_response(req_continue2.req, **{ - 'allThreadsContinued': True - }), - self.new_event('continued', threadId=tid), - self.new_event( - 'output', - category='stdout', - output='== done waiting ==', - ), - #self.new_event( - # 'thread', - # threadId=tid, - # reason='exited', - #), - #self.new_event('exited', exitCode=0), - #self.new_event('terminated'), - ]) - # before attaching - self.assertIn(b'waiting for attach', out1) - self.assertNotIn(b'attached!', out1) - # after attaching - self.assertNotIn('attached!', out2) - # after bp1 continue - self.assertIn('attached!', out3) - self.assertNotIn('done waiting', out3) - # after bp2 continue - self.assertIn('done waiting', out4) - - def test_nodebug(self): - lockfile = self.workspace.lockfile() - done, waitscript = lockfile.wait_in_script() - filename = self.write_script('spam.py', dedent(""" - print('+ before') - - {} - - print('+ after') - """).format(waitscript)) - with DebugClient(port=PORT) as editor: - adapter, session = editor.host_local_debugger( - argv=[ - '--nodebug', - filename, - ], - ) - - (req_initialize, req_launch, req_config, _, _, _ - ) = lifecycle_handshake(session, 'launch') - - done() - adapter.wait() - - received = list(_strip_newline_output_events(session.received)) - self.assert_contains(received[:9], [ - self.new_version_event(session.received), - self.new_response(req_initialize.req, **INITIALIZE_RESPONSE), - self.new_event('initialized'), - self.new_response(req_launch.req), - self.new_response(req_config.req), - self.new_event('output', - output='+ before', - category='stdout'), - self.new_event('output', - output='+ after', - category='stdout'), - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) diff --git a/tests/system_tests/test_misc.py b/tests/system_tests/test_misc.py deleted file mode 100644 index 88946824..00000000 --- a/tests/system_tests/test_misc.py +++ /dev/null @@ -1,347 +0,0 @@ -import os -import os.path -import time -from tests.helpers.resource import TestResources -from . import ( - _strip_newline_output_events, lifecycle_handshake, - LifecycleTestsBase, DebugInfo -) - -TEST_FILES = TestResources.from_module(__name__) - - -class NoOutputTests(LifecycleTestsBase): - def run_test_with_no_output(self, debug_info): - options = {'debugOptions': ['RedirectOutput']} - with self.start_debugging(debug_info) as dbg: - session = dbg.session - lifecycle_handshake(session, debug_info.starttype, - options=options) - out = dbg.adapter._proc.output.decode('utf-8') - self.assertEqual(out, '') - - def test_with_no_output(self): - filename = TEST_FILES.resolve('nooutput.py') - cwd = os.path.dirname(filename) - self.run_test_with_no_output( - DebugInfo(filename=filename, cwd=cwd)) - - -class OutputTests(LifecycleTestsBase): - def run_test_output(self, debug_info, expected_output): - options = {'debugOptions': ['RedirectOutput']} - with self.start_debugging(debug_info) as dbg: - session = dbg.session - lifecycle_handshake(session, debug_info.starttype, - options=options) - out = dbg.adapter._proc.output.decode('utf-8') - self.assertTrue(out.startswith(expected_output)) - received = list(_strip_newline_output_events(session.received)) - self.assert_contains(received, [ - self.new_event('output', category='stdout', output=expected_output) - ]) - - def test_tab_output(self): - filename = TEST_FILES.resolve('output_test.py') - cwd = os.path.dirname(filename) - self.run_test_output( - DebugInfo(filename=filename, cwd=cwd), - 'Hello\tworld') - - -class ThreadCountTests(LifecycleTestsBase): - def run_test_threads(self, debug_info, bp_filename, bp_line, count): - breakpoints = [{ - 'source': { - 'path': bp_filename - }, - 'breakpoints': [{ - 'line': bp_line - }] - }] - with self.start_debugging(debug_info) as dbg: - session = dbg.session - with session.wait_for_event('stopped') as result: - lifecycle_handshake( - session, debug_info.starttype, - breakpoints=breakpoints, - threads=True) - # Give extra time for thread state to be captured - time.sleep(1) - event = result['msg'] - tid = event.body['threadId'] - req_threads = session.send_request('threads') - req_threads.wait() - threads = req_threads.resp.body['threads'] - - session.send_request('continue', threadId=tid) - - self.assertEqual(count, len(threads)) - - def test_single_thread(self): - filename = TEST_FILES.resolve('single_thread.py') - cwd = os.path.dirname(filename) - self.run_test_threads( - DebugInfo(filename=filename, cwd=cwd), - bp_filename=filename, bp_line=2, count=1) - - def test_multi_thread(self): - filename = TEST_FILES.resolve('three_threads.py') - cwd = os.path.dirname(filename) - self.run_test_threads( - DebugInfo(filename=filename, cwd=cwd), - bp_filename=filename, bp_line=22, count=3) - - -class StopOnEntryTests(LifecycleTestsBase): - def run_test_stop_on_entry_enabled(self, debug_info, expected_source_path): - options = {'debugOptions': ['RedirectOutput', 'StopOnEntry']} - with self.start_debugging(debug_info) as dbg: - session = dbg.session - with session.wait_for_event('stopped') as result: - lifecycle_handshake( - session, debug_info.starttype, - options=options) - received_before = self.find_events(session.received, 'output') - - event = result['msg'] - tid = event.body['threadId'] - - req_stacktrace = session.send_request( - 'stackTrace', - threadId=tid, - ) - req_stacktrace.wait() - stacktrace = req_stacktrace.resp.body - - session.send_request('continue', threadId=tid) - - # We should be broken in on the first line: - self.assertGreaterEqual(stacktrace['totalFrames'], 1) - self.assert_is_subset(stacktrace, { - 'stackFrames': [{ - 'id': 1, - 'name': '', - 'source': { - 'path': expected_source_path, - 'sourceReference': 0, - }, - 'line': 1, - 'column': 1, - }], - }) - - # Make sure there is no stdout based output event - for e in received_before: - self.assertFalse(e.body['category'] == 'stdout') - - received = list(_strip_newline_output_events(session.received)) - self.assert_contains(received, [ - self.new_event('continued', threadId=tid), - self.new_event('output', category='stdout', output='one'), - self.new_event('output', category='stdout', output='two'), - self.new_event('output', category='stdout', output='three'), - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) - - def run_test_stop_on_entry_disabled(self, debug_info): - options = {'debugOptions': ['RedirectOutput']} - with self.start_debugging(debug_info) as dbg: - session = dbg.session - lifecycle_handshake( - session, debug_info.starttype, - options=options) - - received = list(_strip_newline_output_events(session.received)) - self.assert_contains(received, [ - self.new_event('output', category='stdout', output='one'), - self.new_event('output', category='stdout', output='two'), - self.new_event('output', category='stdout', output='three'), - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) - - -class StopOnEntryLaunchFileTests(StopOnEntryTests): - def test_stop_on_entry_enabled(self): - filename = TEST_FILES.resolve('stoponentry.py') - cwd = os.path.dirname(filename) - self.run_test_stop_on_entry_enabled( - DebugInfo(filename=filename, cwd=cwd), - filename) - - def test_stop_on_entry_disabled(self): - filename = TEST_FILES.resolve('stoponentry.py') - cwd = os.path.dirname(filename) - self.run_test_stop_on_entry_disabled( - DebugInfo(filename=filename, cwd=cwd)) - - -class StopOnEntryLaunchModuleTests(StopOnEntryTests): - def test_stop_on_entry_enabled(self): - filename = TEST_FILES.resolve('stoponentry.py') - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - self.run_test_stop_on_entry_enabled( - DebugInfo( - modulename='stoponentry', - cwd=cwd, - env=env), - filename) - - def test_stop_on_entry_disabled(self): - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - self.run_test_stop_on_entry_disabled( - DebugInfo( - modulename='stoponentry', - cwd=cwd, - env=env)) - - -class StopOnEntryLaunchPackageTests(StopOnEntryTests): - def test_stop_on_entry_enabled(self): - filename = TEST_FILES.resolve('mymod', '__init__.py') - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - self.run_test_stop_on_entry_enabled( - DebugInfo( - modulename='mymod', - cwd=cwd, - env=env), - filename) - - def test_stop_on_entry_disabled(self): - env = TEST_FILES.env_with_py_path() - cwd = TEST_FILES.root - self.run_test_stop_on_entry_disabled( - DebugInfo( - modulename='mymod', - cwd=cwd, - env=env)) - - -class ShowReturnValueTests(LifecycleTestsBase): - def _debugger_step_next(self, session, thread_id): - stopped = session.get_awaiter_for_event('stopped') - req_next = session.send_request('next', threadId=thread_id) - req_next.wait(timeout=2.0) - stopped.wait(timeout=2.0) - - def _get_variables(self, session, thread_id): - req_stacktrace = session.send_request( - 'stackTrace', - threadId=thread_id, - ) - req_stacktrace.wait(timeout=2.0) - - frames = req_stacktrace.resp.body['stackFrames'] - frame_id = frames[0]['id'] - - req_scopes = session.send_request( - 'scopes', - frameId=frame_id, - ) - req_scopes.wait(timeout=2.0) - - scopes = req_scopes.resp.body['scopes'] - variables_reference = scopes[0]['variablesReference'] - - req_variables = session.send_request( - 'variables', - variablesReference=variables_reference, - ) - req_variables.wait(timeout=2.0) - - return req_variables.resp.body['variables'] - - def run_test_return_values(self, debug_info, is_enabled): - if is_enabled: - options = {'debugOptions': ['RedirectOutput', 'ShowReturnValue']} - else: - options = {'debugOptions': ['RedirectOutput']} - - breakpoints = [{ - 'source': { - 'path': debug_info.filename - }, - 'breakpoints': [{'line': 10}, {'line': 11}, {'line': 12}], - 'lines': [10, 11, 12] - }] - with self.start_debugging(debug_info) as dbg: - session = dbg.session - stopped = session.get_awaiter_for_event('stopped') - (_, req_launch_attach, _, _, _, _ - ) = lifecycle_handshake(session, debug_info.starttype, - options=options, - breakpoints=breakpoints) - req_launch_attach.wait(timeout=3.0) - - stopped.wait() - thread_id = stopped.event.body['threadId'] - - self._debugger_step_next(session, thread_id) - variables = self._get_variables(session, thread_id) - - return_values = list(v for v in variables - if v['name'].startswith('(return) ')) - - if is_enabled: - self.assertEqual(len(return_values), 1) - self.assertEqual(return_values[0]['name'], - '(return) MyClass.do_something') - self.assertEqual(return_values[0]['value'], - "'did something'") - self.assertEqual(return_values[0]['type'], - 'str') - attributes = return_values[0]['presentationHint']['attributes'] - self.assertTrue('readOnly' in attributes) - else: - self.assertEqual(len(return_values), 0) - - self._debugger_step_next(session, thread_id) - variables = self._get_variables(session, thread_id) - - return_values = list(v for v in variables - if v['name'].startswith('(return) ')) - - if is_enabled: - self.assertEqual(len(return_values), 2) - self.assertEqual(return_values[0]['name'], - '(return) MyClass.do_something') - self.assertEqual(return_values[0]['value'], - "'did something'") - self.assertEqual(return_values[0]['type'], - 'str') - attributes = return_values[0]['presentationHint']['attributes'] - self.assertTrue('readOnly' in attributes) - - self.assertEqual(return_values[1]['name'], - '(return) my_func') - self.assertEqual(return_values[1]['value'], - "'did more things'") - self.assertEqual(return_values[1]['type'], - 'str') - attributes = return_values[1]['presentationHint']['attributes'] - self.assertTrue('readOnly' in attributes) - else: - self.assertEqual(len(return_values), 0) - - exited = session.get_awaiter_for_event('exited') - session.send_request('continue').wait(timeout=2.0) - exited.wait(timeout=3.0) - - def test_return_values_enabled(self): - filename = TEST_FILES.resolve('returnvalues.py') - cwd = os.path.dirname(filename) - self.run_test_return_values( - DebugInfo(filename=filename, cwd=cwd), - is_enabled=True) - - def test_return_values_disabled(self): - filename = TEST_FILES.resolve('returnvalues.py') - cwd = os.path.dirname(filename) - self.run_test_return_values( - DebugInfo(filename=filename, cwd=cwd), - is_enabled=False) diff --git a/tests/system_tests/test_remote.py b/tests/system_tests/test_remote.py deleted file mode 100644 index 64d32ff2..00000000 --- a/tests/system_tests/test_remote.py +++ /dev/null @@ -1,292 +0,0 @@ -import os -import os.path -import signal -import sys - -from tests.helpers.resource import TestResources -from tests.helpers.script import find_line -from tests.helpers.socket import resolve_hostname -from . import ( - _strip_newline_output_events, lifecycle_handshake, - LifecycleTestsBase, DebugInfo, PORT, -) - - -TEST_FILES = TestResources.from_module('tests.system_tests.test_basic') -WITH_OUTPUT = TEST_FILES.sub('test_output') -SYSTEM_TEST_FILES = TestResources.from_module('tests.system_tests') -WITH_TEST_FORVER = SYSTEM_TEST_FILES.sub('test_forever') - - -class RemoteTests(LifecycleTestsBase): - def _assert_stacktrace_is_subset(self, stacktrace, expected_stacktrace): - # Ignore path case on Windows. - if sys.platform == 'win32': - for frame in stacktrace.get('stackFrames'): - frame['source']['path'] = frame['source'].get('path', '').upper() # noqa - for frame in expected_stacktrace.get('stackFrames'): - frame['source']['path'] = frame['source'].get('path', '').upper() # noqa - - self.assert_is_subset(stacktrace, expected_stacktrace) - - def run_test_attach(self, debug_info): - options = {'debugOptions': ['RedirectOutput']} - - with self.start_debugging(debug_info) as dbg: - lifecycle_handshake(dbg.session, debug_info.starttype, - options=options) - - received = list(_strip_newline_output_events(dbg.session.received)) - self.assert_contains(received, [ - self.new_event('output', category='stdout', output='yes'), - self.new_event('output', category='stderr', output='no'), - ]) - - def run_test_source_references(self, - debug_info, - expected_stacktrace, - path_mappings=[], - debug_options=[]): - options = { - 'debugOptions': debug_options, - 'pathMappings': path_mappings - } - - with open(debug_info.filename) as scriptfile: - script = scriptfile.read() - bp = find_line(script, 'bp') - - with self.start_debugging(debug_info) as dbg: - lifecycle_handshake(dbg.session, debug_info.starttype, - options=options, - threads=True) - - # wait till we enter the for loop. - with dbg.session.wait_for_event('stopped') as result: - arguments = { - 'source': { - 'name': os.path.basename(debug_info.filename), - 'path': debug_info.filename - }, - 'lines': [bp], - 'breakpoints': [{'line': bp}] - } - dbg.session.send_request('setBreakpoints', **arguments) - event = result['msg'] - tid = event.body['threadId'] - req_stacktrace = dbg.session.send_request( - 'stackTrace', - threadId=tid, - ) - req_stacktrace.wait() - stacktrace = req_stacktrace.resp.body - req_continue = dbg.session.send_request('continue', threadId=tid) - req_continue.wait() - - # Kill remove program. - os.kill(dbg.adapter.pid, signal.SIGTERM) - - self._assert_stacktrace_is_subset(stacktrace, expected_stacktrace) - - -class AttachFileTests(RemoteTests): - - def test_attach_localhost(self): - filename = WITH_OUTPUT.resolve('attach_output.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - self.run_test_attach( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - ), - ) - - def test_attach_127001(self): - filename = WITH_OUTPUT.resolve('attach_output.py') - cwd = os.path.dirname(filename) - argv = ['127.0.0.1', str(PORT)] - self.run_test_attach( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - ), - ) - - def test_attach_0000(self): - filename = WITH_OUTPUT.resolve('attach_output.py') - cwd = os.path.dirname(filename) - argv = ['0.0.0.0', str(PORT)] - self.run_test_attach( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - ), - ) - - def test_attach_byip(self): - filename = WITH_OUTPUT.resolve('attach_output.py') - cwd = os.path.dirname(filename) - argv = ['0.0.0.0', str(PORT)] - ip = resolve_hostname() - - self.run_test_attach( - DebugInfo( - filename=filename, - attachtype='import', - host=ip, - cwd=cwd, - starttype='attach', - argv=argv, - ), - ) - - def test_source_references_should_be_returned_without_path_mappings(self): - filename = WITH_TEST_FORVER.resolve('attach_forever.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - expected_stacktrace = { - 'stackFrames': [{ - 'source': { - 'path': filename, - 'sourceReference': 1, - } - }], - } - self.run_test_source_references( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - ), - expected_stacktrace, - ) - - def test_source_references_should_not_be_returned_with_path_mappings(self): - filename = WITH_TEST_FORVER.resolve('attach_forever.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - path_mappings = [{ - 'localRoot': os.path.dirname(filename), - 'remoteRoot': os.path.dirname(filename) - }] - expected_stacktrace = { - 'stackFrames': [{ - 'source': { - 'path': filename, - 'sourceReference': 0, - } - }], - } - self.run_test_source_references( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - #verbose=True, - ), - expected_stacktrace, - path_mappings, - ) - - def test_source_references_should_be_returned_with_invalid_path_mappings( - self): - filename = WITH_TEST_FORVER.resolve('attach_forever.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - path_mappings = [{ - 'localRoot': os.path.dirname(__file__), - 'remoteRoot': os.path.dirname(__file__) - }] - expected_stacktrace = { - 'stackFrames': [{ - 'source': { - 'path': filename, - 'sourceReference': 1, - } - }], - } - self.run_test_source_references( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - ), - expected_stacktrace, - path_mappings, - ) - - def test_source_references_should_be_returned_with_win_client(self): - filename = WITH_TEST_FORVER.resolve('attach_forever.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - client_dir = 'C:\\Development\\Projects\\src\\sub dir' - path_mappings = [{ - 'localRoot': client_dir, - 'remoteRoot': os.path.dirname(filename) - }] - expected_stacktrace = { - 'stackFrames': [{ - 'source': { - 'path': client_dir + '\\' + os.path.basename(filename), - 'sourceReference': 0, - } - }], - } - self.run_test_source_references( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - ), - expected_stacktrace, - path_mappings=path_mappings, - debug_options=['WindowsClient'], - ) - - def test_source_references_should_be_returned_with_unix_client(self): - filename = WITH_TEST_FORVER.resolve('attach_forever.py') - cwd = os.path.dirname(filename) - argv = ['localhost', str(PORT)] - client_dir = '/Users/PeterSmith/projects/src/sub dir' - path_mappings = [{ - 'localRoot': client_dir, - 'remoteRoot': os.path.dirname(filename) - }] - expected_stacktrace = { - 'stackFrames': [{ - 'source': { - 'path': client_dir + '/' + os.path.basename(filename), - 'sourceReference': 0, - } - }], - } - self.run_test_source_references( - DebugInfo( - filename=filename, - attachtype='import', - cwd=cwd, - starttype='attach', - argv=argv, - ), - expected_stacktrace, - path_mappings=path_mappings, - debug_options=['UnixClient'], - ) diff --git a/tests/system_tests/test_restart_vsc.py b/tests/system_tests/test_restart_vsc.py deleted file mode 100644 index 0c55380e..00000000 --- a/tests/system_tests/test_restart_vsc.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -import os.path -import unittest - -from tests.helpers.resource import TestResources -from . import ( - _strip_newline_output_events, lifecycle_handshake, - LifecycleTestsBase, DebugInfo, -) - - -TEST_FILES = TestResources.from_module('tests.system_tests.test_forever') - - -@unittest.skip('Needs fixing in #530') -class RestartVSCTests(LifecycleTestsBase): - - def test_disconnect_without_restart(self): - filename = TEST_FILES.resolve('launch_forever.py') - cwd = os.path.dirname(filename) - debug_info = DebugInfo(filename=filename, cwd=cwd) - - with self.start_debugging(debug_info) as dbg: - (_, req_launch, _, _, _, _ - ) = lifecycle_handshake(dbg.session, debug_info.starttype) - req_launch.wait() - - dbg.session.send_request('disconnect', restart=False) - - received = list(_strip_newline_output_events(dbg.session.received)) - evts = self.find_events(received, 'terminated') - self.assertEqual(len(evts), 1) - - def test_disconnect_with_restart(self): - filename = TEST_FILES.resolve('launch_forever.py') - cwd = os.path.dirname(filename) - debug_info = DebugInfo(filename=filename, cwd=cwd) - - with self.start_debugging(debug_info) as dbg: - (_, req_launch, _, _, _, _ - ) = lifecycle_handshake(dbg.session, debug_info.starttype) - req_launch.wait() - - dbg.session.send_request('disconnect', restart=True) - - received = list(_strip_newline_output_events(dbg.session.received)) - evts = self.find_events(received, 'terminated') - self.assertEqual(len(evts), 0) diff --git a/tests/system_tests/test_variables.py b/tests/system_tests/test_variables.py deleted file mode 100644 index 336cc4e9..00000000 --- a/tests/system_tests/test_variables.py +++ /dev/null @@ -1,267 +0,0 @@ -import os -import os.path - -from tests.helpers.debugsession import Awaitable -from tests.helpers.resource import TestResources -from . import ( - lifecycle_handshake, LifecycleTestsBase, DebugInfo, -) - - -TEST_FILES = TestResources.from_module(__name__) - - -class VariableTests(LifecycleTestsBase): - - def test_variables(self): - filename = TEST_FILES.resolve('simple.py') - cwd = os.path.dirname(filename) - self.run_test_variables(DebugInfo(filename=filename, cwd=cwd)) - - def run_test_variables(self, debug_info): - bp_line = 3 - breakpoints = [{ - 'source': { - 'path': debug_info.filename - }, - 'breakpoints': [{ - 'line': bp_line - }] - }] - - with self.start_debugging(debug_info) as dbg: - session = dbg.session - with session.wait_for_event('stopped') as result: - (_, req_launch_attach, _, _, _, _, - ) = lifecycle_handshake(session, debug_info.starttype, - breakpoints=breakpoints) - req_launch_attach.wait() - event = result['msg'] - tid = event.body['threadId'] - - req_stacktrace = session.send_request( - 'stackTrace', - threadId=tid, - ) - req_stacktrace.wait() - frames = req_stacktrace.resp.body['stackFrames'] - frame_id = frames[0]['id'] - req_scopes = session.send_request( - 'scopes', - frameId=frame_id, - ) - req_scopes.wait() - scopes = req_scopes.resp.body['scopes'] - variables_reference = scopes[0]['variablesReference'] - req_variables = session.send_request( - 'variables', - variablesReference=variables_reference, - ) - req_variables.wait() - variables = req_variables.resp.body['variables'] - - var_b = list(b for b in variables if b['name'] == 'b') - var_b = var_b[0] if len(var_b) == 1 else None - if var_b is None: - var_b_variables = None - else: - var_b_ref = var_b['variablesReference'] - req_variables = session.send_request( - 'variables', - variablesReference=var_b_ref, - ) - req_variables.wait() - var_b_variables = req_variables.resp.body['variables'] - - req_evaluate1 = session.send_request( - 'evaluate', - expression='a', - frameId=frame_id, - ) - req_evaluate2 = session.send_request( - 'evaluate', - expression="b['one']", - frameId=frame_id, - ) - Awaitable.wait_all(req_evaluate1, req_evaluate2) - var_a_evaluate = req_evaluate1.resp.body - var_b_one_evaluate = req_evaluate2.resp.body - - session.send_request('continue', threadId=tid) - - # Variables for a, b, __file__, __main__ - self.assertGreaterEqual(len(variables), 3) - self.assert_is_subset(variables, [{ - 'name': 'a', - 'type': 'int', - 'value': '1', - 'evaluateName': 'a' - }, { - 'name': 'b', - 'type': 'dict', - 'value': "{'one': 1, 'two': 2}", - 'evaluateName': 'b' - }, { - 'name': '__builtins__', - 'type': 'dict', - 'evaluateName': '__builtins__' - }, { - 'name': '__doc__', - 'type': 'NoneType', - 'value': 'None', - 'evaluateName': '__doc__' - }, { - 'name': '__file__', - 'type': 'str', - 'presentationHint': { - 'attributes': ['rawString'] - }, - 'evaluateName': '__file__' - }, { - 'name': '__loader__', - 'type': 'SourceFileLoader', - 'evaluateName': '__loader__' - }, { - 'name': '__name__', - 'type': 'str', - 'value': "'__main__'", - 'presentationHint': { - 'attributes': ['rawString'] - }, - 'evaluateName': '__name__' - }, { - 'name': '__package__', - 'type': 'NoneType', - 'value': 'None', - 'evaluateName': '__package__' - }, { - 'name': '__spec__', - 'type': 'NoneType', - 'value': 'None', - 'evaluateName': '__spec__' - }]) - self.assertEqual(var_a_evaluate, { - 'type': 'int', - 'result': '1', - }) - - assert var_b_variables is not None - self.assert_is_subset(var_b_variables, [{ - 'type': 'int', - 'value': '1', - 'evaluateName': "b['one']" - }, { - 'type': 'int', - 'value': '2', - 'evaluateName': "b['two']" - }, { - 'name': '__len__', - 'type': 'int', - 'value': '2', - 'evaluateName': 'b.__len__' - }]) - - self.assertEqual(var_b_one_evaluate, { - 'type': 'int', - 'result': '1', - }) - - def test_variable_sorting(self): - filename = TEST_FILES.resolve('for_sorting.py') - cwd = os.path.dirname(filename) - self.run_test_variable_sorting(DebugInfo(filename=filename, cwd=cwd)) - - def run_test_variable_sorting(self, debug_info): - bp_line = 16 - breakpoints = [{ - 'source': { - 'path': debug_info.filename - }, - 'breakpoints': [{ - 'line': bp_line - }] - }] - - with self.start_debugging(debug_info) as dbg: - session = dbg.session - with session.wait_for_event('stopped') as result: - (_, req_launch_attach, _, _, _, _, - ) = lifecycle_handshake(session, debug_info.starttype, - breakpoints=breakpoints) - req_launch_attach.wait() - - event = result['msg'] - tid = event.body['threadId'] - - req_stacktrace = session.send_request( - 'stackTrace', - threadId=tid, - ) - req_stacktrace.wait() - frames = req_stacktrace.resp.body['stackFrames'] - frame_id = frames[0]['id'] - req_scopes = session.send_request( - 'scopes', - frameId=frame_id, - ) - req_scopes.wait() - scopes = req_scopes.resp.body['scopes'] - variables_reference = scopes[0]['variablesReference'] - req_variables = session.send_request( - 'variables', - variablesReference=variables_reference, - ) - req_variables.wait() - variables = req_variables.resp.body['variables'] - - b_dict_vars = list(v for v in variables if v['name'] == 'b_test') - if not b_dict_vars: - b_dict_var_items = None - else: - b_dict_var, = b_dict_vars - b_dict_var_ref = b_dict_var['variablesReference'] - req_variables = session.send_request( - 'variables', - variablesReference=b_dict_var_ref, - ) - req_variables.wait() - b_dict_var_items = req_variables.resp.body['variables'] - - #c_dict_vars = list(v for v in variables if v['name'] == 'c_test') - #if not c_dict_vars: - # c_dict_var_items = None - #else: - # c_dict_var, = c_dict_vars - # c_dict_var_ref = c_dict_var['variablesReference'] - # req_variables = session.send_request( - # 'variables', - # variablesReference=c_dict_var_ref, - # ) - # req_variables.wait() - # c_dict_var_items = req_variables.resp.body['variables'] - - session.send_request('continue', threadId=tid) - - variables_to_check = list(v['name'] - for v in variables - if v['name'].find('_test') > 0) - expected_var_order = [ - 'a_test', 'b_test', 'c_test', '_a_test', '_b_test', '_c_test', - '__a_test', '__b_test', '__c_test', '__a_test__', '__b_test__', - '__c_test__' - ] - self.assertEqual(expected_var_order, variables_to_check) - - # Dict keys are sorted normally, i.e., the '_' rules don't apply - # TODO: v['name'][1:5] is needed due to bug #45 - b_dict_var_keys = list(v['name'][1:5] - for v in b_dict_var_items - if not v['name'].startswith('__')) - expected_b_dict_var_keys_order = ['abcd', 'eggs', 'spam'] - self.assertEqual(b_dict_var_keys, expected_b_dict_var_keys_order) - - # TODO: Numeric dict keys have following issues - # bug: #45 and #213 - # c_dict_var_keys = list(v['name'] for v in c_dict_var_items) - # expected_c_dict_var_keys_order = ['1', '2', '10', '__len__'] - # self.assertEqual(c_dict_var_keys, expected_c_dict_var_keys_order) diff --git a/tests/system_tests/test_wait_on_exit.py b/tests/system_tests/test_wait_on_exit.py deleted file mode 100644 index 376eee9f..00000000 --- a/tests/system_tests/test_wait_on_exit.py +++ /dev/null @@ -1,124 +0,0 @@ -import os -import os.path -import time - -from tests.helpers.resource import TestResources -from . import ( - lifecycle_handshake, LifecycleTestsBase, DebugInfo, -) - -TEST_FILES = TestResources.from_module(__name__) - - -class WaitOnExitTests(LifecycleTestsBase): - def _wait_for_output(self, session): - for _ in range(50): - time.sleep(0.1) - out = '\t'.join(e.body['output'] for e in - self.find_events(session.received, 'output') - if e.body['category'] == 'stdout') - if not out.find('Ready') == -1: - return True - return False - - def run_test_wait_on_exit(self, debug_info, dbg_options, expect_timeout): - options = {'debugOptions': ['RedirectOutput'] + dbg_options} - launched = False - exception_occurred = False - try: - with self.start_debugging(debug_info) as dbg: - session = dbg.session - (_, req_launch_attach, _, _, _, _ - ) = lifecycle_handshake(session, debug_info.starttype, - options=options) - req_launch_attach.wait(timeout=2.0) - - # wait for the test program initiate exit - self.assertTrue(self._wait_for_output(session)) - launched = True - except Exception as ex: - dbg.adapter._proc.terminate() - if not launched: - raise - if not expect_timeout: - raise - exception_occurred = True - text = 'Timeout waiting for process to die' - self.assertTrue(ex.args[0].find(text) > -1) - self.assertEqual(expect_timeout, exception_occurred) - - -class LaunchFileWaitOnExitTests(WaitOnExitTests): - def test_wait_on_normal_exit_enabled(self): - filename = TEST_FILES.resolve('waitonexit.py') - cwd = os.path.dirname(filename) - env = {'PTVSD_NORMAL_EXIT': 'True'} - debug_info = DebugInfo(filename=filename, cwd=cwd, env=env) - self.run_test_wait_on_exit(debug_info, - dbg_options=['WaitOnNormalExit'], - expect_timeout=True) - - def test_wait_on_normal_exit_disabled(self): - filename = TEST_FILES.resolve('waitonexit.py') - cwd = os.path.dirname(filename) - env = {'PTVSD_NORMAL_EXIT': 'True'} - debug_info = DebugInfo(filename=filename, cwd=cwd, env=env) - self.run_test_wait_on_exit(debug_info, - dbg_options=[], - expect_timeout=False) - - def test_wait_on_abnormal_exit_enabled(self): - filename = TEST_FILES.resolve('waitonexit.py') - cwd = os.path.dirname(filename) - debug_info = DebugInfo(filename=filename, cwd=cwd) - self.run_test_wait_on_exit(debug_info, - dbg_options=['WaitOnAbnormalExit'], - expect_timeout=True) - - def test_wait_on_abnormal_exit_disabled(self): - filename = TEST_FILES.resolve('waitonexit.py') - cwd = os.path.dirname(filename) - debug_info = DebugInfo(filename=filename, cwd=cwd) - self.run_test_wait_on_exit(debug_info, - dbg_options=['WaitOnNormalExit'], - expect_timeout=False) - - -class LaunchModuleWaitOnExitTests(WaitOnExitTests): - def test_wait_on_normal_exit_enabled(self): - module_name = 'waitonexit' - cwd = TEST_FILES.parent.root - env = TEST_FILES.env_with_py_path() - env['PTVSD_NORMAL_EXIT'] = 'True' - debug_info = DebugInfo(modulename=module_name, env=env, cwd=cwd) - self.run_test_wait_on_exit(debug_info, - dbg_options=['WaitOnNormalExit'], - expect_timeout=True) - - def test_wait_on_normal_exit_disabled(self): - module_name = 'waitonexit' - cwd = TEST_FILES.parent.root - env = TEST_FILES.env_with_py_path() - env['PTVSD_NORMAL_EXIT'] = 'True' - debug_info = DebugInfo(modulename=module_name, env=env, cwd=cwd) - self.run_test_wait_on_exit(debug_info, - dbg_options=[], - expect_timeout=False) - - def test_wait_on_abnormal_exit_enabled(self): - module_name = 'waitonexit' - cwd = TEST_FILES.parent.root - env = TEST_FILES.env_with_py_path() - debug_info = DebugInfo(modulename=module_name, env=env, cwd=cwd) - self.run_test_wait_on_exit(debug_info, - dbg_options=['WaitOnAbnormalExit'], - expect_timeout=True) - - def test_wait_on_abnormal_exit_disabled(self): - module_name = 'waitonexit' - cwd = TEST_FILES.parent.root - env = TEST_FILES.env_with_py_path() - debug_info = DebugInfo(modulename=module_name, env=env, cwd=cwd) - self.run_test_wait_on_exit(debug_info, - dbg_options=['WaitOnNormalExit'], - expect_timeout=False) diff --git a/tests/system_tests/test_web_frameworks.py b/tests/system_tests/test_web_frameworks.py deleted file mode 100644 index 198c0d07..00000000 --- a/tests/system_tests/test_web_frameworks.py +++ /dev/null @@ -1,628 +0,0 @@ -import os -import os.path -import re -import threading -import time - -from tests.helpers.resource import TestResources -from tests.helpers.webhelper import get_web_string_no_error -from . import ( - _strip_newline_output_events, lifecycle_handshake, - LifecycleTestsBase, DebugInfo, PORT -) - - -TEST_FILES = TestResources.from_module(__name__) -re_link = r"(http(s|)\:\/\/[\w\.]*\:[0-9]{4,6}(\/|))" - - -class WebFrameworkTests(LifecycleTestsBase): - def _get_url_from_output(self, session): - strings = (e.body['output'] for e in - self.find_events(session.received, 'output')) - for s in strings: - matches = re.findall(re_link, s) - if matches and matches[0]and matches[0][0].strip(): - return matches[0][0] - return None - - def _wait_for_server_url(self, session): - # wait for web server start by looking for - # server url - for _ in range(10): - try: - # wait for an output event. - session.get_awaiter_for_event('output').wait(timeout=3.0) - path = self._get_url_from_output(session) - if path is not None: - return path - except TimeoutError: - pass - return self._get_url_from_output(session) - - def run_test_with_break_points(self, debug_info, **kwargs): - bp_filename = kwargs.pop('bp_filename') - bp_line = kwargs.pop('bp_line') - bp_name = kwargs.pop('bp_name') - bp_var_value = kwargs.pop('bp_var_value') - framework = kwargs.pop('framework', 'Django') - if (debug_info.starttype == 'attach'): - pathMappings = [] - pathMappings.append({ - 'localRoot': debug_info.cwd, - 'remoteRoot': debug_info.cwd - }) - options = { - 'debugOptions': ['RedirectOutput', framework], - 'pathMappings': pathMappings - } - else: - options = {'debugOptions': ['RedirectOutput', framework]} - - breakpoints = [{ - 'source': { - 'path': bp_filename - }, - 'breakpoints': [{ - 'line': bp_line - }] - }] - - with self.start_debugging(debug_info) as dbg: - session = dbg.session - - stopped = session.get_awaiter_for_event( - 'stopped', - condition=lambda msg: msg.body['reason'] == 'breakpoint') - (_, req_launch_attach, _, _, _, _, - ) = lifecycle_handshake(session, debug_info.starttype, - options=options, - breakpoints=breakpoints) - req_launch_attach.wait() - - path = self._wait_for_server_url(session) - # Give web server some time for finish writing output - time.sleep(1) - - # connect to web server - web_result = {} - web_client_thread = threading.Thread( - target=get_web_string_no_error, - args=(path, web_result), - name='test.webClient' - ) - - web_client_thread.start() - - stopped.wait(timeout=5.0) - tid = stopped.event.body['threadId'] - - req_stacktrace = session.send_request( - 'stackTrace', - threadId=tid, - ) - req_stacktrace.wait() - stacktrace = req_stacktrace.resp.body - - frame_id = stacktrace['stackFrames'][0]['id'] - req_scopes = session.send_request( - 'scopes', - frameId=frame_id, - ) - req_scopes.wait() - scopes = req_scopes.resp.body['scopes'] - variables_reference = scopes[0]['variablesReference'] - req_variables = session.send_request( - 'variables', - variablesReference=variables_reference, - ) - req_variables.wait() - variables = req_variables.resp.body['variables'] - - continued = session.get_awaiter_for_event('continued') - session.send_request( - 'continue', - threadId=tid, - ) - continued.wait(timeout=3.0) - - # wait for flask rendering thread to exit - web_client_thread.join(timeout=0.1) - - # shutdown to web server - path += 'exit' if path.endswith('/') else '/exit' - web_client_thread = threading.Thread( - target=get_web_string_no_error, - args=(path, None), - name='test.webClient.shutdown' - ) - web_client_thread.start() - web_client_thread.join(timeout=1) - - received = list(_strip_newline_output_events(session.received)) - - self.assertGreaterEqual(stacktrace['totalFrames'], 1) - self.assert_is_subset(stacktrace, { - # We get Python and PTVSD frames as well. - # 'totalFrames': 2, - 'stackFrames': [{ - 'id': 1, - 'name': bp_name, - 'source': { - 'sourceReference': 0, - 'path': bp_filename - }, - 'line': bp_line, - 'column': 1, - }], - }) - variables = list(v for v in variables if v['name'] == 'content') - self.assert_is_subset(variables, [{ - 'name': 'content', - 'type': 'str', - 'value': repr(bp_var_value), - 'presentationHint': {'attributes': ['rawString']}, - 'evaluateName': 'content' - }]) - self.assertTrue(web_result['content'].find(bp_var_value) != -1) - self.assert_contains(received, [ - self.new_event( - 'stopped', - reason='breakpoint', - threadId=tid, - text=None, - description=None, - preserveFocusHint=False, - ), - self.new_event('continued', threadId=tid), - ]) - - if framework != 'Django': - # TODO: Figure out better way to shutdown Django - self.assert_contains(received, [ - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) - - def run_test_with_handled_exception(self, debug_info, framework, - expected_source_name): - if (debug_info.starttype == 'attach'): - pathMappings = [] - pathMappings.append({ - 'localRoot': debug_info.cwd, - 'remoteRoot': debug_info.cwd - }) - options = { - 'debugOptions': ['RedirectOutput', framework], - 'pathMappings': pathMappings - } - else: - options = {'debugOptions': ['RedirectOutput', framework]} - - excbreakpoints = [{'filters': ['raised', 'uncaught']}] - with self.start_debugging(debug_info) as dbg: - session = dbg.session - - stopped = session.get_awaiter_for_event('stopped') - (_, req_launch_attach, _, _, _, _, - ) = lifecycle_handshake(session, debug_info.starttype, - options=options, - excbreakpoints=excbreakpoints) - req_launch_attach.wait() - - base_path = self._wait_for_server_url(session) - # Give web server some time for finish writing output - time.sleep(1) - - # connect to web server - path = base_path + \ - 'handled' if base_path.endswith('/') else '/handled' - web_result = {} - web_client_thread = threading.Thread( - target=get_web_string_no_error, - args=(path, web_result), - name='test.webClient' - ) - - web_client_thread.start() - - stopped.wait(timeout=5.0) - thread_id = stopped.event.body['threadId'] - - req_exc_info = session.send_request( - 'exceptionInfo', - threadId=thread_id, - ) - req_exc_info.wait() - exc_info = req_exc_info.resp.body - - self.assert_is_subset( - exc_info, { - 'exceptionId': 'ArithmeticError', - 'breakMode': 'always', - 'details': { - 'typeName': 'ArithmeticError', - 'source': expected_source_name - } - }) - - continued = session.get_awaiter_for_event('continued') - session.send_request( - 'continue', - threadId=thread_id, - ) - continued.wait(timeout=3.0) - - # Shutdown webserver - path = base_path + 'exit' if base_path.endswith('/') else '/exit' - web_client_thread = threading.Thread( - target=get_web_string_no_error, - args=(path, None), - name='test.webClient.shutdown' - ) - web_client_thread.start() - web_client_thread.join(timeout=1) - - received = list(_strip_newline_output_events(dbg.session.received)) - if framework != 'Django': - # TODO: Figure out better way to shutdown Django - self.assert_contains(received, [ - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) - - def run_test_with_unhandled_exception(self, debug_info, framework, - expected_source_name): - if (debug_info.starttype == 'attach'): - pathMappings = [] - pathMappings.append({ - 'localRoot': debug_info.cwd, - 'remoteRoot': debug_info.cwd - }) - options = { - 'debugOptions': ['RedirectOutput', framework], - 'pathMappings': pathMappings - } - else: - options = {'debugOptions': ['RedirectOutput', framework]} - - excbreakpoints = [{'filters': ['raised', 'uncaught']}] - with self.start_debugging(debug_info) as dbg: - session = dbg.session - - stopped = session.get_awaiter_for_event('stopped') - (_, req_launch_attach, _, _, _, _, - ) = lifecycle_handshake(session, debug_info.starttype, - options=options, - excbreakpoints=excbreakpoints) - req_launch_attach.wait() - - base_path = self._wait_for_server_url(session) - # Give web server some time for finish writing output - time.sleep(1) - - # connect to web server - path = base_path + \ - 'unhandled' if base_path.endswith('/') else '/unhandled' - web_result = {} - web_client_thread = threading.Thread( - target=get_web_string_no_error, - args=(path, web_result), - name='test.webClient' - ) - - web_client_thread.start() - - stopped.wait(timeout=5.0) - thread_id = stopped.event.body['threadId'] - - req_exc_info = dbg.session.send_request( - 'exceptionInfo', - threadId=thread_id, - ) - req_exc_info.wait() - exc_info = req_exc_info.resp.body - - self.assert_is_subset( - exc_info, { - 'exceptionId': 'ArithmeticError', - 'breakMode': 'always', - 'details': { - 'typeName': 'ArithmeticError', - 'source': expected_source_name - } - }) - - continued = session.get_awaiter_for_event('continued') - session.send_request( - 'continue', - threadId=thread_id, - ) - continued.wait(timeout=3.0) - - # Shutdown webserver - path = base_path + 'exit' if base_path.endswith('/') else '/exit' - web_client_thread = threading.Thread( - target=get_web_string_no_error, - args=(path, None), - name='test.webClient.shutdown' - ) - web_client_thread.start() - web_client_thread.join(timeout=1) - - received = list(_strip_newline_output_events(dbg.session.received)) - if framework != 'Django': - # TODO: Figure out better way to shutdown Django - self.assert_contains(received, [ - self.new_event('exited', exitCode=0), - self.new_event('terminated'), - ]) - - -class FlaskLaunchTests(WebFrameworkTests): - def test_with_route_break_points(self): - filename = TEST_FILES.resolve('flask', 'launch', 'app.py') - cwd = os.path.dirname(filename) - self.run_test_with_break_points( - DebugInfo( - modulename='flask', - argv=['run', '--no-debugger', '--no-reload', '--with-threads'], - env={ - 'FLASK_APP': 'app.py', - 'FLASK_ENV': 'development', - 'FLASK_DEBUG': '0', - 'LC_ALL': 'C.UTF-8', - 'LANG': 'C.UTF-8', - }, - cwd=cwd), - framework='Jinja', - bp_filename=filename, bp_line=11, bp_name='home', - bp_var_value='Flask-Jinja-Test') - - def test_with_template_break_points(self): - filename = TEST_FILES.resolve('flask', 'launch', 'app.py') - template = TEST_FILES.resolve( - 'flask', 'launch', 'templates', 'hello.html') - cwd = os.path.dirname(filename) - self.run_test_with_break_points( - DebugInfo( - modulename='flask', - argv=['run', '--no-debugger', '--no-reload', '--with-threads'], - env={ - 'FLASK_APP': 'app.py', - 'FLASK_ENV': 'development', - 'FLASK_DEBUG': '0', - 'LC_ALL': 'C.UTF-8', - 'LANG': 'C.UTF-8', - }, - cwd=cwd), - framework='Jinja', - bp_filename=template, bp_line=8, bp_name='template', - bp_var_value='Flask-Jinja-Test') - - def test_with_handled_exceptions(self): - filename = TEST_FILES.resolve('flask', 'launch', 'app.py') - cwd = os.path.dirname(filename) - self.run_test_with_handled_exception( - DebugInfo( - modulename='flask', - argv=['run', '--no-debugger', '--no-reload', '--with-threads'], - env={ - 'FLASK_APP': 'app.py', - 'FLASK_ENV': 'development', - 'FLASK_DEBUG': '0', - 'LC_ALL': 'C.UTF-8', - 'LANG': 'C.UTF-8', - }, - cwd=cwd), 'Jinja', filename) - - def test_with_unhandled_exceptions(self): - filename = TEST_FILES.resolve('flask', 'launch', 'app.py') - cwd = os.path.dirname(filename) - self.run_test_with_unhandled_exception( - DebugInfo( - modulename='flask', - argv=['run', '--no-debugger', '--no-reload', '--with-threads'], - env={ - 'FLASK_APP': 'app.py', - 'FLASK_ENV': 'development', - 'FLASK_DEBUG': '0', - 'LC_ALL': 'C.UTF-8', - 'LANG': 'C.UTF-8', - }, - cwd=cwd), 'Jinja', filename) - - -class FlaskAttachTests(WebFrameworkTests): - def test_with_route_break_points(self): - filename = TEST_FILES.resolve('flask', 'attach', 'app.py') - cwd = os.path.dirname(filename) - self.run_test_with_break_points( - DebugInfo( - starttype='attach', - modulename='flask', - argv=['run', '--no-debugger', '--no-reload', '--with-threads'], - env={ - 'FLASK_APP': 'app.py', - 'FLASK_ENV': 'development', - 'FLASK_DEBUG': '0', - 'LC_ALL': 'C.UTF-8', - 'LANG': 'C.UTF-8', - 'PTVSD_HOST': 'localhost', - 'PTVSD_PORT': str(PORT), - }, - cwd=cwd), - framework='Jinja', - bp_filename=filename, bp_line=19, bp_name='home', - bp_var_value='Flask-Jinja-Test') - - def test_with_template_break_points(self): - filename = TEST_FILES.resolve('flask', 'attach', 'app.py') - template = TEST_FILES.resolve( - 'flask', 'attach', 'templates', 'hello.html') - cwd = os.path.dirname(filename) - self.run_test_with_break_points( - DebugInfo( - starttype='attach', - modulename='flask', - argv=['run', '--no-debugger', '--no-reload', '--with-threads'], - env={ - 'FLASK_APP': 'app.py', - 'FLASK_ENV': 'production', - 'LC_ALL': 'C.UTF-8', - 'LANG': 'C.UTF-8', - 'PTVSD_HOST': 'localhost', - 'PTVSD_PORT': str(PORT), - }, - cwd=cwd), - framework='Jinja', - bp_filename=template, bp_line=8, bp_name='template', - bp_var_value='Flask-Jinja-Test') - - def test_with_handled_exceptions(self): - filename = TEST_FILES.resolve('flask', 'attach', 'app.py') - cwd = os.path.dirname(filename) - self.run_test_with_handled_exception( - DebugInfo( - starttype='attach', - modulename='flask', - argv=['run', '--no-debugger', '--no-reload', '--with-threads'], - env={ - 'FLASK_APP': 'app.py', - 'FLASK_ENV': 'production', - 'LC_ALL': 'C.UTF-8', - 'LANG': 'C.UTF-8', - 'PTVSD_HOST': 'localhost', - 'PTVSD_PORT': str(PORT), - }, - cwd=cwd), 'Jinja', filename) - - def test_with_unhandled_exceptions(self): - filename = TEST_FILES.resolve('flask', 'attach', 'app.py') - cwd = os.path.dirname(filename) - self.run_test_with_unhandled_exception( - DebugInfo( - starttype='attach', - modulename='flask', - argv=['run', '--no-debugger', '--no-reload', '--with-threads'], - env={ - 'FLASK_APP': 'app.py', - 'FLASK_ENV': 'production', - 'LC_ALL': 'C.UTF-8', - 'LANG': 'C.UTF-8', - 'PTVSD_HOST': 'localhost', - 'PTVSD_PORT': str(PORT), - }, - cwd=cwd), 'Jinja', filename) - - -class DjangoLaunchTests(WebFrameworkTests): - def test_with_route_break_points(self): - filename = TEST_FILES.resolve('django', 'launch', 'app.py') - cwd = os.path.dirname(filename) - self.run_test_with_break_points( - DebugInfo( - filename=filename, - argv=['runserver', '--noreload', '--nothreading'], - cwd=cwd), - framework='Django', - bp_filename=filename, bp_line=40, bp_name='home', - bp_var_value='Django-Django-Test') - - def test_with_template_break_points(self): - filename = TEST_FILES.resolve('django', 'launch', 'app.py') - template = TEST_FILES.resolve( - 'django', 'launch', 'templates', 'hello.html') - cwd = os.path.dirname(filename) - self.run_test_with_break_points( - DebugInfo( - filename=filename, - argv=['runserver', '--noreload', '--nothreading'], - cwd=cwd), - framework='Django', - bp_filename=template, bp_line=8, bp_name='Django Template', - bp_var_value='Django-Django-Test') - - def test_with_handled_exceptions(self): - filename = TEST_FILES.resolve('django', 'launch', 'app.py') - cwd = os.path.dirname(filename) - self.run_test_with_handled_exception( - DebugInfo( - filename=filename, - argv=['runserver', '--noreload', '--nothreading'], - cwd=cwd), 'Django', filename) - - def test_with_unhandled_exceptions(self): - filename = TEST_FILES.resolve('django', 'launch', 'app.py') - cwd = os.path.dirname(filename) - self.run_test_with_unhandled_exception( - DebugInfo( - filename=filename, - argv=['runserver', '--noreload', '--nothreading'], - cwd=cwd), 'Django', filename) - - -class DjangoAttachTests(WebFrameworkTests): - def test_with_route_break_points(self): - filename = TEST_FILES.resolve('django', 'attach', 'app.py') - cwd = os.path.dirname(filename) - self.run_test_with_break_points( - DebugInfo( - filename=filename, - starttype='attach', - argv=['runserver', '--noreload', '--nothreading'], - env={ - 'PTVSD_HOST': 'localhost', - 'PTVSD_PORT': str(PORT), - }, - cwd=cwd), - framework='Django', - bp_filename=filename, bp_line=47, bp_name='home', - bp_var_value='Django-Django-Test') - - def test_with_template_break_points(self): - filename = TEST_FILES.resolve('django', 'attach', 'app.py') - template = TEST_FILES.resolve( - 'django', 'attach', 'templates', 'hello.html') - cwd = os.path.dirname(filename) - self.run_test_with_break_points( - DebugInfo( - filename=filename, - starttype='attach', - argv=['runserver', '--noreload', '--nothreading'], - env={ - 'PTVSD_HOST': 'localhost', - 'PTVSD_PORT': str(PORT), - }, - cwd=cwd), - framework='Django', - bp_filename=template, bp_line=8, bp_name='Django Template', - bp_var_value='Django-Django-Test') - - def test_with_handled_exceptions(self): - filename = TEST_FILES.resolve('django', 'attach', 'app.py') - cwd = os.path.dirname(filename) - self.run_test_with_handled_exception( - DebugInfo( - filename=filename, - starttype='attach', - argv=['runserver', '--noreload', '--nothreading'], - env={ - 'PTVSD_HOST': 'localhost', - 'PTVSD_PORT': str(PORT), - }, - cwd=cwd), 'Django', filename) - - def test_with_unhandled_exceptions(self): - filename = TEST_FILES.resolve('django', 'attach', 'app.py') - cwd = os.path.dirname(filename) - self.run_test_with_unhandled_exception( - DebugInfo( - filename=filename, - starttype='attach', - argv=['runserver', '--noreload', '--nothreading'], - env={ - 'PTVSD_HOST': 'localhost', - 'PTVSD_PORT': str(PORT), - }, - cwd=cwd), 'Django', filename) diff --git a/pytests/test_debugger.py b/tests/test_debugger.py similarity index 100% rename from pytests/test_debugger.py rename to tests/test_debugger.py diff --git a/pytests/test_internals_filter.py b/tests/test_internals_filter.py similarity index 100% rename from pytests/test_internals_filter.py rename to tests/test_internals_filter.py diff --git a/pytests/test_messaging.py b/tests/test_messaging.py similarity index 100% rename from pytests/test_messaging.py rename to tests/test_messaging.py diff --git a/pytests/test_modules_manager.py b/tests/test_modules_manager.py similarity index 98% rename from pytests/test_modules_manager.py rename to tests/test_modules_manager.py index ac051a71..513bdc7d 100644 --- a/pytests/test_modules_manager.py +++ b/tests/test_modules_manager.py @@ -7,7 +7,7 @@ import threading import time import ptvsd.untangle -from pytests.helpers.pattern import ANY +from tests.helpers.pattern import ANY from ptvsd.wrapper import ModulesManager diff --git a/pytests/test_parse_args.py b/tests/test_parse_args.py similarity index 100% rename from pytests/test_parse_args.py rename to tests/test_parse_args.py diff --git a/pytests/test_pathutils.py b/tests/test_pathutils.py similarity index 100% rename from pytests/test_pathutils.py rename to tests/test_pathutils.py diff --git a/pytests/test_safe_repr.py b/tests/test_safe_repr.py similarity index 100% rename from pytests/test_safe_repr.py rename to tests/test_safe_repr.py diff --git a/pytests/test_socket.py b/tests/test_socket.py similarity index 100% rename from pytests/test_socket.py rename to tests/test_socket.py diff --git a/tests/test_tests___main__.py b/tests/test_tests___main__.py deleted file mode 100644 index c968c537..00000000 --- a/tests/test_tests___main__.py +++ /dev/null @@ -1,164 +0,0 @@ -import os -import os.path -import unittest -import sys - -from . import TEST_ROOT, PROJECT_ROOT -from .__main__ import convert_argv - - -class ConvertArgsTests(unittest.TestCase): - - def test_no_args(self): - config, argv, env = convert_argv([]) - - self.assertEqual(argv, [ - sys.executable + ' -m unittest', - 'discover', - '--top-level-directory', PROJECT_ROOT, - '--start-directory', PROJECT_ROOT, - ]) - self.assertEqual(env, { - 'HAS_NETWORK': '1', - }) - self.assertFalse(config.lint_only) - self.assertFalse(config.lint) - - def test_discovery_full(self): - config, argv, env = convert_argv([ - '-v', '--failfast', '--full', - ]) - - self.assertEqual(argv, [ - sys.executable + ' -m unittest', - 'discover', - '--top-level-directory', PROJECT_ROOT, - '--start-directory', PROJECT_ROOT, - '-v', '--failfast', - ]) - self.assertEqual(env, { - 'HAS_NETWORK': '1', - }) - self.assertFalse(config.lint_only) - self.assertFalse(config.lint) - - def test_discovery_quick(self): - config, argv, env = convert_argv([ - '-v', '--failfast', '--quick', - ]) - - self.assertEqual(argv, [ - sys.executable + ' -m unittest', - 'discover', - '--top-level-directory', PROJECT_ROOT, - '--start-directory', os.path.join(TEST_ROOT, 'ptvsd'), - '-v', '--failfast', - ]) - self.assertEqual(env, { - 'HAS_NETWORK': '1', - }) - self.assertFalse(config.lint_only) - self.assertFalse(config.lint) - - def test_modules(self): - config, argv, env = convert_argv([ - '-v', '--failfast', - 'w', - 'x/y.py:Spam.test_spam'.replace('/', os.sep), - 'z:Eggs', - ]) - - self.assertEqual(argv, [ - sys.executable + ' -m unittest', - '-v', '--failfast', - 'w', - 'x.y.Spam.test_spam', - 'z.Eggs', - ]) - self.assertEqual(env, { - 'HAS_NETWORK': '1', - }) - self.assertFalse(config.lint_only) - self.assertFalse(config.lint) - - def test_no_network(self): - config, argv, env = convert_argv([ - '--no-network' - ]) - - self.assertEqual(argv, [ - sys.executable + ' -m unittest', - 'discover', - '--top-level-directory', PROJECT_ROOT, - '--start-directory', PROJECT_ROOT, - ]) - self.assertEqual(env, {}) - self.assertFalse(config.lint_only) - self.assertFalse(config.lint) - - def test_lint(self): - config, argv, env = convert_argv([ - '-v', - '--quick', - '--lint' - ]) - - self.assertEqual(argv, [ - sys.executable + ' -m unittest', - 'discover', - '--top-level-directory', PROJECT_ROOT, - '--start-directory', os.path.join(TEST_ROOT, 'ptvsd'), - '-v', - ]) - self.assertEqual(env, { - 'HAS_NETWORK': '1', - }) - - self.assertFalse(config.lint_only) - self.assertTrue(config.lint) - self.assertTrue(config.quick) - - def test_lint_only(self): - config, _, _ = convert_argv([ - '--quick', '--lint-only', '-v', - ]) - - self.assertTrue(config.lint_only) - self.assertFalse(config.lint) - self.assertTrue(config.quick) - - def test_coverage(self): - config, argv, env = convert_argv([ - '--coverage' - ]) - - self.assertEqual(argv, [ - sys.executable + ' -m unittest', - 'discover', - '--top-level-directory', PROJECT_ROOT, - '--start-directory', PROJECT_ROOT, - ]) - self.assertEqual(env, { - 'HAS_NETWORK': '1', - }) - self.assertFalse(config.lint_only) - self.assertFalse(config.lint) - self.assertTrue(config.coverage) - - def test_specify_junit_file(self): - config, argv, env = convert_argv([ - '--junit-xml=./my-test-file' - ]) - - self.assertEqual(argv, [ - sys.executable + ' -m unittest', - 'discover', - '--top-level-directory', PROJECT_ROOT, - '--start-directory', PROJECT_ROOT, - ]) - self.assertEqual(env, { - 'HAS_NETWORK': '1', - }) - self.assertFalse(config.lint_only) - self.assertFalse(config.lint) - self.assertEqual(config.junit_xml, './my-test-file')