Repository: microsoft/ptvsd

Issue:
on_evaluate_request must handle case where thread_id is None.
See: https://github.com/Microsoft/ptvsd/issues/1326#issuecomment-489111943 for details.


Discussion:

Resolving patch:
diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py
--- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py
+++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py
@@ -9,7 +9,7 @@
 from _pydevd_bundle._debug_adapter import pydevd_base_schema
 from _pydevd_bundle._debug_adapter.pydevd_schema import (SourceBreakpoint, ScopesResponseBody, Scope,
     VariablesResponseBody, SetVariableResponseBody, ModulesResponseBody, SourceResponseBody,
-    GotoTargetsResponseBody, ExceptionOptions, SetExpressionResponseBody)
+    GotoTargetsResponseBody, ExceptionOptions, SetExpressionResponseBody, EvaluateResponseBody)
 from _pydevd_bundle._debug_adapter.pydevd_schema import CompletionsResponseBody
 from _pydevd_bundle.pydevd_api import PyDevdAPI
 from _pydevd_bundle.pydevd_comm_constants import (
@@ -627,8 +627,19 @@ def on_evaluate_request(self, py_db, request):
         thread_id = py_db.suspended_frames_manager.get_thread_id_for_variable_reference(
             arguments.frameId)
 
-        self.api.request_exec_or_evaluate_json(
-            py_db, request, thread_id)
+        if thread_id is not None:
+            self.api.request_exec_or_evaluate_json(
+                py_db, request, thread_id)
+        else:
+            body = EvaluateResponseBody('', 0)
+            response = pydevd_base_schema.build_response(
+                request,
+                kwargs={
+                    'body': body,
+                    'success': False,
+                    'message': 'Unable to find thread for evaluation.'
+            })
+            return NetCommand(CMD_RETURN, 0, response, is_json=True)
 
     def on_setexpression_request(self, py_db, request):
         # : :type arguments: SetExpressionArguments
@@ -644,7 +655,7 @@ def on_setexpression_request(self, py_db, request):
             response = pydevd_base_schema.build_response(
                 request,
                 kwargs={
-                    'body':body,
+                    'body': body,
                     'success': False,
                     'message': 'Unable to find thread to set expression.'
             })
@@ -676,7 +687,7 @@ def on_variables_request(self, py_db, request):
             variables = []
             body = VariablesResponseBody(variables)
             variables_response = pydevd_base_schema.build_response(request, kwargs={
-                'body':body,
+                'body': body,
                 'success': False,
                 'message': 'Unable to find thread to evaluate variable reference.'
             })
@@ -695,7 +706,7 @@ def on_setvariable_request(self, py_db, request):
             variables_response = pydevd_base_schema.build_response(
                 request,
                 kwargs={
-                    'body':body,
+                    'body': body,
                     'success': False,
                     'message': 'Unable to find thread to evaluate variable reference.'
             })


Test patch:
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
--- a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
+++ b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
@@ -1464,6 +1464,43 @@ def test_evaluate(case_setup):
         writer.finished_ok = True
 
 
+def test_evaluate_failures(case_setup):
+    with case_setup.test_file('_debugger_case_completions.py') as writer:
+        json_facade = JsonFacade(writer)
+
+        json_facade.write_set_breakpoints(writer.get_line_index_with_content('Break here'))
+        json_facade.write_make_initial_run()
+
+        json_hit = json_facade.wait_for_thread_stopped()
+
+        # First, try with wrong id.
+        exec_request = json_facade.write_request(
+            pydevd_schema.EvaluateRequest(pydevd_schema.EvaluateArguments('a = 10', frameId=9999, context='repl')))
+        exec_response = json_facade.wait_for_response(exec_request)
+        assert exec_response.success == False
+        assert exec_response.message == 'Wrong ID sent from the client: 9999'
+
+        first_hit = None
+        for i in range(2):
+            json_hit = json_facade.get_stack_as_json_hit(json_hit.thread_id)
+            if i == 0:
+                first_hit = json_hit
+
+            if i == 1:
+                # Now, check with a previously existing frameId.
+                exec_request = json_facade.write_request(
+                    pydevd_schema.EvaluateRequest(pydevd_schema.EvaluateArguments('a = 10', frameId=first_hit.frame_id, context='repl')))
+                exec_response = json_facade.wait_for_response(exec_request)
+                assert exec_response.success == False
+                assert exec_response.message == 'Unable to find thread for evaluation.'
+
+            json_facade.write_continue(wait_for_response=i == 0)
+            if i == 0:
+                json_hit = json_facade.wait_for_thread_stopped()
+
+        writer.finished_ok = True
+
+
 @pytest.mark.parametrize('max_frames', ['default', 'all', 10])  # -1 = default, 0 = all, 10 = 10 frames
 def test_exception_details(case_setup, max_frames):
     with case_setup.test_file('_debugger_case_large_exception_stack.py') as writer: