@@ -926,6 +926,80 @@ def additional_output_checks(writer, stdout, stderr):
926926 writer .finished_ok = True
927927
928928
929+ def test_case_chained_exception_variables (case_setup_dap , pyfile ):
930+ """
931+ When stopped on a chained exception, variable evaluation must work for
932+ frames belonging to the chained (cause) exception, not just the primary one.
933+ """
934+
935+ @pyfile
936+ def target ():
937+ def inner ():
938+ cause_var = "from_cause" # noqa
939+ raise RuntimeError ("the cause" )
940+
941+ def outer ():
942+ outer_var = "from_outer" # noqa
943+ try :
944+ inner ()
945+ except Exception as e :
946+ raise ValueError ("the effect" ) from e # raise line
947+
948+ outer ()
949+
950+ def check_test_suceeded_msg (self , stdout , stderr ):
951+ return "the cause" in "" .join (stderr )
952+
953+ def additional_output_checks (writer , stdout , stderr ):
954+ assert 'raise RuntimeError("the cause")' in stderr
955+ assert 'raise ValueError("the effect") from e' in stderr
956+
957+ with case_setup_dap .test_file (
958+ target ,
959+ EXPECTED_RETURNCODE = 1 ,
960+ check_test_suceeded_msg = check_test_suceeded_msg ,
961+ additional_output_checks = additional_output_checks ,
962+ ) as writer :
963+ json_facade = JsonFacade (writer )
964+
965+ json_facade .write_launch (justMyCode = False )
966+ json_facade .write_set_exception_breakpoints (["uncaught" ])
967+ json_facade .write_make_initial_run ()
968+
969+ json_hit = json_facade .wait_for_thread_stopped (
970+ reason = "exception" , line = writer .get_line_index_with_content ("raise line" )
971+ )
972+
973+ stack_frames = json_hit .stack_trace_response .body .stackFrames
974+
975+ # Find the chained exception frames.
976+ chained_frames = [f for f in stack_frames if f ["name" ].startswith ("[Chained Exc:" )]
977+ assert len (chained_frames ) > 0 , "Expected chained exception frames in stack trace"
978+
979+ # Verify variables can be retrieved for chained frames (this is the
980+ # operation that previously failed with "Unable to find thread to
981+ # evaluate variable reference.").
982+ for chained_frame in chained_frames :
983+ variables_response = json_facade .get_variables_response (chained_frame ["id" ])
984+ assert variables_response .success
985+
986+ # Find the inner() chained frame and verify its local variable.
987+ inner_frames = [f for f in chained_frames if "inner" in f ["name" ]]
988+ assert len (inner_frames ) == 1
989+ variables_response = json_facade .get_variables_response (inner_frames [0 ]["id" ])
990+ var_names = [v ["name" ] for v in variables_response .body .variables ]
991+ assert "cause_var" in var_names , "Expected 'cause_var' in chained frame variables, got: %s" % var_names
992+
993+ # Also verify that primary frame variables still work.
994+ primary_frame_id = json_hit .frame_id
995+ variables_response = json_facade .get_variables_response (primary_frame_id )
996+ assert variables_response .success
997+
998+ json_facade .write_continue ()
999+
1000+ writer .finished_ok = True
1001+
1002+
9291003def test_case_throw_exc_reason_shown (case_setup_dap ):
9301004
9311005 def check_test_suceeded_msg (self , stdout , stderr ):
0 commit comments