2 from __future__
import absolute_import
, division
, print_function
, unicode_literals
11 from dataclasses
import dataclass
24 from libcst
.metadata
import CodeRange
, MetadataWrapper
, PositionProvider
25 from lspcommand
import LspCommandProcessor
, Transcript
, TranscriptEntry
30 interpolate_variables
,
31 uninterpolate_variables
,
39 "_WaitForNotificationSpec",
40 "_WaitForRequestSpec",
41 "_WaitForResponseSpec",
42 "_WaitForHhServerReadySpec",
46 # pyre-fixme[5]: Global expression must be annotated.
47 _LspIdMap
= Mapping
[_MessageSpec
, Json
]
49 _Traceback
= Sequence
[inspect
.FrameInfo
]
59 """Indicates that no response should be sent (different from `None` since
60 `None` is a valid JSON value)."""
66 """Get the line number that this function was called at.
68 Previously, we used to do this automatically whenever we called
69 `.request`. However, a recent upgrade of Python breaks that functionality
70 for chained function calls in some cases, and it instead reports the line
71 number of the first function call in the chain. We use `line()` to ensure
72 that we don't have a chained function call and can get the line number
75 cf
= inspect
.currentframe()
78 ), "Must be able to get current call frame to produce error messages for test"
79 # pyre-fixme[16]: `Optional` has no attribute `f_lineno`.
80 return cf
.f_back
.f_lineno
84 """Represents an LSP test to be run, in a declarative fashion.
86 Since `LspTestSpec`s are just values, they can be composed using regular
87 functions. For example, you can make an `initialize_spec` function that
88 returns an `LspTestSpec` with the `initialize` request already sent and
91 def __init__(self
, name
: str) -> None:
93 self
._messages
: Sequence
["_MessageSpec"] = []
94 self
._ignored
_notification
_methods
: AbstractSet
[str] = set()
95 # pyre-fixme[11]: Annotation `Json` is not defined as a type.
96 self
._ignored
_requests
: Sequence
[Tuple
[str, Json
]] = []
97 self
._ignore
_status
_diagnostics
: bool = False
99 def ignore_notifications(self
, *, method
: str) -> "LspTestSpec":
100 ignored_notification_methods
= set(self
._ignored
_notification
_methods
)
101 ignored_notification_methods
.add(method
)
102 return self
._update
(ignored_notification_methods
=ignored_notification_methods
)
104 def ignore_status_diagnostics(self
, value
: bool) -> "LspTestSpec":
105 return self
._update
(ignore_status_diagnostics
=value
)
108 self
, *, method
: str, params
: Json
, comment
: Optional
[str] = None
110 ignored_requests
= list(self
._ignored
_requests
)
111 ignored_requests
.append((method
, params
))
112 return self
._update
(ignored_requests
=ignored_requests
)
121 wait_id
: Optional
[str] = None,
122 comment
: Optional
[str] = None,
123 powered_by
: Optional
[str] = None,
125 traceback
= inspect
.stack()
126 assert traceback
is not None, "Failed to get traceback info"
128 messages
= list(self
._messages
)
129 if wait_id
is not None and any(
130 isinstance(message
, _RequestSpec
) and message
.wait_id
== wait_id
131 for message
in messages
133 raise ValueError(f
"Duplicate wait ID: {wait_id}")
142 powered_by
=powered_by
,
143 call_site_info
=_CallSiteInfo(line_num
=line
, traceback
=traceback
),
146 return self
._update
(messages
=messages
)
148 def debug(self
) -> "LspTestSpec":
149 """Issue a `telemetry/rage` request for debugging.
151 The language server has to support the `telemetry/rage` request. Once
152 the response is received, its debugging output is rendered in the test
153 output. This can be useful when trying to debug the internal state of
156 The test will not pass while there's a `debug()` statement in its spec,
157 so it must be removed before committing the code.
159 messages
= list(self
._messages
)
160 messages
.append(_DebugRequestSpec())
161 return self
._update
(messages
=messages
)
164 self
, method
: str, params
: Json
, *, comment
: Optional
[str] = None
166 messages
= list(self
._messages
)
168 _NotificationSpec(method
=method
, params
=params
, comment
=comment
)
170 return self
._update
(messages
=messages
)
172 def wait_for_server_request(
177 result
: Union
[Json
, NoResponse
],
178 comment
: Optional
[str] = None,
180 messages
= list(self
._messages
)
183 method
=method
, params
=params
, result
=result
, comment
=comment
186 return self
._update
(messages
=messages
)
188 def wait_for_notification(
189 self
, method
: str, params
: Json
, *, comment
: Optional
[str] = None
191 messages
= list(self
._messages
)
193 _WaitForNotificationSpec(method
=method
, params
=params
, comment
=comment
)
195 return self
._update
(messages
=messages
)
197 def wait_for_response(self
, wait_id
: str) -> "LspTestSpec":
198 messages
= list(self
._messages
)
199 messages
.append(_WaitForResponseSpec(wait_id
=wait_id
))
200 return self
._update
(messages
=messages
)
202 def wait_for_hh_server_ready(self
) -> "LspTestSpec":
203 messages
= list(self
._messages
)
204 messages
.append(_WaitForHhServerReadySpec())
205 return self
._update
(messages
=messages
)
207 def start_hh_server(self
, comment
: str) -> "LspTestSpec":
211 method
="$test/startHhServer",
214 powered_by
="serverless_ide",
217 def stop_hh_server(self
, comment
: str) -> "LspTestSpec":
221 method
="$test/stopHhServer",
224 powered_by
="serverless_ide",
230 comment
: Optional
[str] = None,
232 contents
: Optional
[str],
234 wait
: Optional
[bool] = None,
236 """Write a file to disk in the middle of the LSP test.
238 If `contents` is `None`, delete the file from disk.
240 If `notify` is `True`, also send a `workspace/didChangeWatchedFiles`
241 notification to the language server corresponding to the file you just
242 changed. The test will then wait for serverless IDE to process the file
243 change before proceeding, unless `wait` is set to `False`.
245 messages
= list(self
._messages
)
248 method
="$test/writeToDisk",
249 params
={"uri": uri
, "contents": contents
},
256 method
="workspace/didChangeWatchedFiles",
257 params
={"changes": [{"uri": uri
, "type": 2}]},
265 _WaitForNotificationSpec(
267 f
"Waiting for change to URI {uri} to be processed... "
268 + "(set wait=False on the corresponding `write_to_disk` call " # noqa: B950
269 + "if this is undesirable)"
271 method
="telemetry/event",
274 "message": "[client-ide] Done processing file changes",
278 return self
._update
(messages
=messages
)
281 self
, lsp_command_processor
: LspCommandProcessor
, variables
: VariableMap
282 ) -> Tuple
[Transcript
, Optional
[str]]:
283 """Run the test given the LSP command processor.
285 Raises an exception with useful debugging information if the test fails."""
286 (json_commands
, lsp_id_map
) = self
._get
_json
_commands
(variables
=variables
)
287 transcript
= lsp_command_processor
.communicate(json_commands
=json_commands
)
289 self
._verify
_transcript
(
290 variables
=variables
, transcript
=transcript
, lsp_id_map
=lsp_id_map
294 num_errors
= len(errors
)
296 f
"Test case {self.name} failed with {num_errors} errors:\n\n"
298 for i
, error
in enumerate(errors
, 1):
299 error_details
+= f
"Error {i}/{num_errors}:\n"
300 error_details
+= str(error
) + "\n"
301 error_details
+= """\
302 If you want to examine the raw LSP logs, you can check the `.sent.log` and
303 `.received.log` files that were generated in the template repo for this test."""
306 return (transcript
, error_details
)
312 messages
: Optional
[Sequence
["_MessageSpec"]] = None,
313 ignored_notification_methods
: Optional
[AbstractSet
[str]] = None,
314 ignored_requests
: Optional
[Sequence
[Tuple
[str, Json
]]] = None,
315 ignore_status_diagnostics
: Optional
[bool] = None,
317 spec
= copy
.copy(self
)
318 if messages
is not None:
319 spec
._messages
= messages
320 if ignored_notification_methods
is not None:
321 spec
._ignored
_notification
_methods
= ignored_notification_methods
322 if ignored_requests
is not None:
323 spec
._ignored
_requests
= ignored_requests
324 if ignore_status_diagnostics
:
325 spec
._ignore
_status
_diagnostics
= ignore_status_diagnostics
328 def _get_json_commands(
330 variables
: VariableMap
331 # pyre-fixme[11]: Annotation `_LspIdMap` is not defined as a type.
332 ) -> Tuple
[Sequence
[Json
], "_LspIdMap"]:
333 """Transforms this test spec into something the LSP command processor
338 for message
in self
._messages
:
340 lsp_id_map
[message
] = current_id
342 if isinstance(message
, _RequestSpec
):
343 json_commands
.append(
346 "comment": message
.comment
,
348 "method": message
.method
,
349 "params": interpolate_variables(
350 message
.params
, variables
=variables
355 if message
.wait_id
is None:
356 # Assume that if no wait ID was explicitly passed, we want
357 # to wait on the response before sending the next message.
358 json_commands
.append(
361 "method": "$test/waitForResponse",
362 "params": {"id": current_id
},
365 elif isinstance(message
, _DebugRequestSpec
):
366 json_commands
.append(
370 "method": "telemetry/rage",
374 elif isinstance(message
, _NotificationSpec
):
375 json_commands
.append(
378 "comment": message
.comment
,
379 "method": message
.method
,
380 "params": interpolate_variables(
381 message
.params
, variables
=variables
385 elif isinstance(message
, _WaitForRequestSpec
):
387 "method": message
.method
,
388 "params": interpolate_variables(
389 message
.params
, variables
=variables
392 if not isinstance(message
.result
, NoResponse
):
393 params
["result"] = message
.result
394 json_commands
.append(
397 "comment": message
.comment
,
398 "method": "$test/waitForRequest",
402 elif isinstance(message
, _WaitForNotificationSpec
):
403 json_commands
.append(
406 "comment": message
.comment
,
407 "method": "$test/waitForNotification",
409 "method": message
.method
,
410 "params": interpolate_variables(
411 message
.params
, variables
=variables
416 elif isinstance(message
, _WaitForResponseSpec
):
419 for previous_message
, lsp_id
in lsp_id_map
.items()
420 if isinstance(previous_message
, _RequestSpec
)
421 and previous_message
.wait_id
== message
.wait_id
423 assert len(lsp_ids
) == 1, (
424 f
"Should have had exactly one previous message with wait ID {message.wait_id!r}, " # noqa: B950
425 + "but got {len(lsp_ids)}"
429 json_commands
.append(
432 "method": "$test/waitForResponse",
433 "params": {"id": lsp_id
},
436 elif isinstance(message
, _WaitForHhServerReadySpec
):
437 json_commands
.append(
440 "method": "$test/waitForHhServerReady",
445 raise ValueError(f
"unhandled message type {message.__class__.__name__}")
446 return (json_commands
, lsp_id_map
)
448 def _verify_transcript(
449 self
, *, variables
: VariableMap
, transcript
: Transcript
, lsp_id_map
: "_LspIdMap"
450 ) -> Iterable
["_ErrorDescription"]:
451 handled_entries
= set()
453 for message
in self
._messages
:
454 lsp_id
= lsp_id_map
[message
]
455 if isinstance(message
, _RequestSpec
):
456 transcript_id
= LspCommandProcessor
._client
_request
_id
(lsp_id
)
457 handled_entries
.add(transcript_id
)
458 assert transcript_id
in transcript
, (
459 f
"Expected message with ID {lsp_id!r} "
460 + f
"to have an entry in the transcript "
461 + f
"under key {transcript_id!r}, "
462 + f
"but it was not found. Transcript: {transcript!r}"
464 entry
= transcript
[transcript_id
]
465 error_description
= self
._verify
_request
(
466 variables
=variables
, entry
=entry
, lsp_id
=lsp_id
, request
=message
468 if error_description
is not None:
469 yield error_description
470 elif isinstance(message
, _DebugRequestSpec
):
471 transcript_id
= LspCommandProcessor
._client
_request
_id
(lsp_id
)
472 handled_entries
.add(transcript_id
)
473 assert transcript_id
in transcript
, (
474 f
"Expected message with ID {lsp_id!r} "
475 + f
"to have an entry in the transcript "
476 + f
"under key {transcript_id!r}, "
477 + f
"but it was not found. Transcript: {transcript!r}"
479 entry
= transcript
[transcript_id
]
480 error_description
= self
._render
_telemetry
_rage
(
481 debug_request
=message
, result
=entry
.received
["result"]
483 yield error_description
484 elif isinstance(message
, _NotificationSpec
):
485 # Nothing needs to be done here, since we sent the notification
486 # and don't expect a response.
492 _WaitForNotificationSpec
,
493 _WaitForResponseSpec
,
494 _WaitForHhServerReadySpec
,
497 # Nothing needs to be done here -- if we failed to wait for the
498 # message, an exception will have been thrown at the
499 # `LspCommandProcessor` layer.
502 raise ValueError(f
"unhandled message type {message.__class__.__name__}")
504 handled_entries |
= set(self
._find
_ignored
_transcript
_ids
(transcript
))
505 yield from self
._flag
_unhandled
_messages
(
506 handled_entries
, variables
, transcript
, lsp_id_map
512 variables
: VariableMap
,
514 entry
: TranscriptEntry
,
515 request
: "_RequestSpec",
516 ) -> Optional
["_ErrorDescription"]:
517 actual_result
= entry
.received
.get("result")
518 actual_powered_by
= entry
.received
.get("powered_by")
519 if request
.comment
is not None:
520 request_description
= (
521 f
"Request with ID {lsp_id!r} (comment: {request.comment!r})"
524 request_description
= f
"Request with ID {lsp_id!r}"
526 # Because of the way hack allocates a different HHI folder for each running
527 # process, let's replace the standard HHI foldername
528 actual_result
= fixup_hhi_json(actual_result
)
529 expected_result
= interpolate_variables(
530 payload
=request
.result
, variables
=variables
532 expected_result
= fixup_hhi_json(expected_result
)
534 if actual_result
!= expected_result
:
535 error_description
= self
._pretty
_print
_diff
(
536 actual
=actual_result
, expected
=expected_result
539 {request_description} got an incorrect result:
541 {error_description}"""
542 request_context
= self
._get
_context
_for
_call
_site
_info
(
543 request
.call_site_info
546 This was the associated request:
549 remediation
= self
._describe
_response
_for
_remediation
(
550 variables
=variables
, request
=request
, actual_response
=entry
.received
552 return _ErrorDescription(
553 description
=description
, context
=context
, remediation
=remediation
555 elif entry
.received
.get("powered_by") != request
.powered_by
:
557 {request_description} had an incorrect value for the `powered_by` field
558 (expected {request.powered_by!r}; got {actual_powered_by!r})
560 request_context
= self
._get
_context
_for
_call
_site
_info
(
561 request
.call_site_info
564 This was the associated request:
567 remediation
= self
._describe
_response
_for
_remediation
(
568 variables
=variables
, request
=request
, actual_response
=entry
.received
570 return _ErrorDescription(
571 description
=description
, context
=context
, remediation
=remediation
574 def _get_context_for_call_site_info(self
, call_site_info
: _CallSiteInfo
) -> str:
575 # Find the first caller frame that isn't in this source file. The
576 # assumption is that the first such frame is in the test code.
578 frame
for frame
in call_site_info
.traceback
if frame
.filename
!= __file__
580 source_filename
= caller_frame
.filename
581 with
open(source_filename
) as f
:
582 source_text
= f
.read()
585 start_line_num_0idx_incl
,
586 end_line_num_0idx_incl
,
587 ) = self
._find
_line
_range
_for
_function
_call
(
588 file_contents
=source_text
, line_num_1idx
=call_site_info
.line_num
590 return self
._pretty
_print
_file
_context
(
591 file_path
=source_filename
,
592 file_contents
=source_text
,
593 start_line_num_0idx_incl
=start_line_num_0idx_incl
,
594 end_line_num_0idx_incl
=end_line_num_0idx_incl
,
597 def _find_line_range_for_function_call(
598 self
, file_contents
: str, line_num_1idx
: int
599 ) -> Tuple
[int, int]:
600 tree
= libcst
.parse_module(file_contents
)
601 function_call_finder
= _FunctionCallFinder()
602 MetadataWrapper(tree
).visit(function_call_finder
)
603 function_calls_containing_line
= [
605 for node
, node_range
in function_call_finder
.function_calls
606 if node_range
.start
.line
<= line_num_1idx
<= node_range
.end
.line
609 function_calls_containing_line
,
610 key
=lambda node_with_range
: node_with_range
[1].end
.line
611 - node_with_range
[1].start
.line
,
613 start_line_num_0idx_incl
= node_range
.start
.line
- 1
614 end_line_num_0idx_incl
= node_range
.end
.line
- 1
615 return (start_line_num_0idx_incl
, end_line_num_0idx_incl
)
617 def _pretty_print_file_context(
621 start_line_num_0idx_incl
: int,
622 end_line_num_0idx_incl
: int,
624 source_lines
= file_contents
.splitlines(keepends
=True)
625 context_lines
= source_lines
[
626 start_line_num_0idx_incl
: end_line_num_0idx_incl
+ 1
629 # Include the line number in a gutter for display.
630 f
"{line_num:>5} | {line_contents}"
631 for line_num
, line_contents
in enumerate(
632 context_lines
, start
=start_line_num_0idx_incl
+ 1
635 file_context
= "".join(context_lines
)
637 # The full path is likely not useful, since it includes any temporary
638 # directories that Buck introduced.
639 prefix
= os
.path
.commonprefix([file_path
, __file__
])
640 display_filename
= file_path
[len(prefix
) :]
641 return display_filename
+ "\n" + file_context
643 def _describe_response_for_remediation(
644 self
, variables
: VariableMap
, request
: "_RequestSpec", actual_response
: Json
646 method
= request
.method
647 params
= request
.params
648 result
= uninterpolate_variables(
649 payload
=actual_response
.get("result"), variables
=variables
651 powered_by
= actual_response
.get("powered_by")
653 request_snippet
= f
"""\
656 if request
.comment
is not None:
657 request_snippet
+= f
"""
658 comment={request.comment!r},"""
659 request_snippet
+= f
"""
662 result={result!r},"""
663 if request
.wait_id
is not None:
664 request_snippet
+= f
"""
665 wait_id={request.wait_id!r},"""
666 if powered_by
is not None:
667 request_snippet
+= f
"""
668 powered_by={powered_by!r},"""
669 request_snippet
+= f
"""
673 1) If this was unexpected, then the language server is buggy and should be
676 2) If this was expected, you can update your request with the following code to
683 def _find_ignored_transcript_ids(self
, transcript
: Transcript
) -> Iterable
[str]:
684 for transcript_id
, entry
in transcript
.items():
686 entry
.received
is not None
687 and "id" not in entry
.received
688 and entry
.received
.get("method") in self
._ignored
_notification
_methods
693 entry
.received
is not None
694 and "id" not in entry
.received
695 and self
._ignore
_status
_diagnostics
696 and entry
.received
["method"] == "textDocument/publishDiagnostics"
697 and entry
.received
["params"].get("isStatusFB")
702 entry
.received
is not None
703 and "id" in entry
.received
704 and "method" in entry
.received
705 and "params" in entry
.received
706 and (entry
.received
["method"], entry
.received
["params"])
707 in self
._ignored
_requests
711 def _flag_unhandled_messages(
713 handled_entries
: AbstractSet
[str],
714 variables
: VariableMap
,
715 transcript
: Transcript
,
716 lsp_id_map
: _LspIdMap
,
717 ) -> Iterable
["_ErrorDescription"]:
718 for transcript_id
, entry
in transcript
.items():
719 if transcript_id
in handled_entries
:
722 received
= entry
.received
726 if entry
.sent
is not None:
727 # We received a request and responded to it.
730 method
= received
["method"]
731 params
= received
["params"]
732 payload
= self
._pretty
_print
_snippet
(received
)
735 An unexpected request of type {method!r} was sent by the language server.
736 Here is the request payload:
740 at_nocommit
= "@" + "nocommit"
742 1) If this was unexpected, then the language server is buggy and should be
745 2) If all requests of type {method!r} with theses params should be ignored,
746 add this directive anywhere in your test:
748 .{self.ignore_requests.__name__}(method={method!r}, params={params!r})
750 3) To handle this request, add this directive to your test to wait for it and
751 respond to it before proceeding:
753 .{self.wait_for_server_request.__name__}(
757 "{at_nocommit}": "fill in request data here",
763 isinstance(message
, _WaitForNotificationSpec
)
764 and message
.method
== method
765 and interpolate_variables(
766 payload
=message
.params
, variables
=variables
769 for message
in self
._messages
771 # This was a notification we we explicitly waiting for, so skip
775 uninterpolated_params
= uninterpolate_variables(
776 payload
=params
, variables
=variables
779 An unexpected notification of type {method!r} was sent by the language server.
780 Here is the notification payload:
785 1) If this was unexpected, then the language server is buggy and should be
788 2) If all notifications of type {method!r} should be ignored, add this directive
789 anywhere in your test:
791 .{self.ignore_notifications.__name__}(method={method!r})
793 3) If this single instance of the notification was expected, add this directive
794 to your test to wait for it before proceeding:
796 .{self.wait_for_notification.__name__}(
798 params={uninterpolated_params!r},
802 previous_request
= self
._find
_previous
_request
(
803 transcript
, lsp_id_map
, current_id
=transcript_id
805 if previous_request
is not None:
806 request_context
= self
._get
_context
_for
_call
_site
_info
(
807 previous_request
.call_site_info
810 request_context
= "<no previous request was found>"
812 This was the most recent request issued from the language client before it
813 received the notification:
817 yield _ErrorDescription(
818 description
=description
, context
=context
, remediation
=remediation
821 def _find_previous_request(
822 self
, transcript
: Transcript
, lsp_id_map
: _LspIdMap
, current_id
: str
823 ) -> Optional
["_RequestSpec"]:
824 previous_transcript_entries
= itertools
.takewhile(
825 lambda kv
: kv
[0] != current_id
, transcript
.items()
827 previous_request_entries
= [
829 for _id
, entry
in previous_transcript_entries
830 if entry
.sent
is not None and LspCommandProcessor
._is
_request
(entry
.sent
)
832 if previous_request_entries
:
833 previous_request_lsp_id
= previous_request_entries
[-1]["id"]
837 [corresponding_request
] = [
839 for request
, lsp_id
in lsp_id_map
.items()
840 if lsp_id
== previous_request_lsp_id
843 corresponding_request
, _RequestSpec
844 ), "We should have identified a client-to-server request at this point"
845 return corresponding_request
847 def _render_telemetry_rage(
848 self
, debug_request
: "_DebugRequestSpec", result
: Json
849 ) -> "_ErrorDescription":
855 data
= row
.get("data")
858 ### Section {title} ###
862 sections
= textwrap
.indent("".join(sections
), prefix
=" ")
864 Here are the results of issuing a `telemetry/rage` request to the language
872 Remove this `debug` request once you're done debugging.
874 return _ErrorDescription(
875 description
=description
, context
=context
, remediation
=remediation
878 def _pretty_print_snippet(self
, obj
: object) -> str:
879 return textwrap
.indent(pprint
.pformat(obj
), prefix
=" ")
881 def _pretty_print_diff(self
, actual
: object, expected
: object) -> str:
882 # Similar to the standard library's `unittest` module:
883 # https://github.com/python/cpython/blob/35d9c37e271c35b87d64cc7422600e573f3ee244/Lib/unittest/case.py#L1147-L1149 # noqa B950
885 "(+ is expected lines, - is actual lines)\n"
888 pprint
.pformat(actual
).splitlines(),
889 pprint
.pformat(expected
).splitlines(),
899 class _FunctionCallFinder(libcst
.CSTVisitor
):
900 """Find function calls and their locations in the given syntax tree.
902 Chained function calls include the entire chain as the callee. For example,
903 the chain `x().y().z()` might include `x().y().z` as the callee and `()` as
904 the function call itself. But in the case of function call chains, we really
905 want just the range covered by the parentheses.
907 However, that's not directly available in `libcst`, so we approximate this
908 by finding the location of `z` and assume that's where the function call
912 METADATA_DEPENDENCIES
= (PositionProvider
,)
914 def __init__(self
) -> None:
915 self
.function_calls
: List
[Tuple
[libcst
.Call
, CodeRange
]] = []
917 def visit_Call(self
, node
: libcst
.Call
) -> None:
918 node_range
= self
.get_metadata(PositionProvider
, node
)
920 start_node
= node
.func
921 while isinstance(start_node
, libcst
.Attribute
):
922 start_node
= start_node
.attr
923 start_node_range
= self
.get_metadata(PositionProvider
, start_node
)
924 start_position
= start_node_range
.start
925 end_position
= node_range
.end
926 node_range
= CodeRange(start
=start_position
, end
=end_position
)
928 self
.function_calls
.append((node
, node_range
))
948 wait_id
: Optional
[str],
949 comment
: Optional
[str],
950 powered_by
: Optional
[str],
951 call_site_info
: _CallSiteInfo
,
953 # pyre-fixme[4]: Attribute must be annotated.
955 # pyre-fixme[4]: Attribute must be annotated.
957 # pyre-fixme[4]: Attribute must be annotated.
959 # pyre-fixme[4]: Attribute must be annotated.
960 self
.wait_id
= wait_id
961 # pyre-fixme[4]: Attribute must be annotated.
962 self
.comment
= comment
963 # pyre-fixme[4]: Attribute must be annotated.
964 self
.powered_by
= powered_by
965 # pyre-fixme[4]: Attribute must be annotated.
966 self
.call_site_info
= call_site_info
969 class _DebugRequestSpec
:
973 class _NotificationSpec
:
974 __slots__
= ["method", "params", "comment"]
976 def __init__(self
, *, method
: str, params
: Json
, comment
: Optional
[str]) -> None:
977 # pyre-fixme[4]: Attribute must be annotated.
979 # pyre-fixme[4]: Attribute must be annotated.
981 # pyre-fixme[4]: Attribute must be annotated.
982 self
.comment
= comment
985 class _WaitForRequestSpec
:
986 __slots__
= ["method", "params", "result", "comment"]
993 result
: Union
[Json
, NoResponse
],
994 comment
: Optional
[str],
996 # pyre-fixme[4]: Attribute must be annotated.
998 # pyre-fixme[4]: Attribute must be annotated.
1000 # pyre-fixme[4]: Attribute must be annotated.
1001 self
.result
= result
1002 # pyre-fixme[4]: Attribute must be annotated.
1003 self
.comment
= comment
1006 class _WaitForNotificationSpec
:
1007 __slots__
= ["method", "params", "comment"]
1009 def __init__(self
, *, method
: str, params
: Json
, comment
: Optional
[str]) -> None:
1010 # pyre-fixme[4]: Attribute must be annotated.
1011 self
.method
= method
1012 # pyre-fixme[4]: Attribute must be annotated.
1013 self
.params
= params
1014 # pyre-fixme[4]: Attribute must be annotated.
1015 self
.comment
= comment
1018 class _WaitForResponseSpec
:
1019 __slots__
= ["wait_id"]
1021 def __init__(self
, *, wait_id
: str) -> None:
1022 # pyre-fixme[4]: Attribute must be annotated.
1023 self
.wait_id
= wait_id
1026 class _WaitForHhServerReadySpec
:
1030 class _ErrorDescription
:
1031 def __init__(self
, description
: str, context
: str, remediation
: str) -> None:
1032 self
.description
= description
1033 self
.context
= context
1034 self
.remediation
= remediation
1036 def __str__(self
) -> str:
1038 Description: {self.description}
1040 if self
.context
is not None:
1047 {self.remediation}"""