|  | # Copyright (C) 2019-2024 Free Software Foundation, Inc. | 
|  | # This program is free software; you can redistribute it and/or modify | 
|  | # it under the terms of the GNU General Public License as published by | 
|  | # the Free Software Foundation; either version 3 of the License, or | 
|  | # (at your option) any later version. | 
|  | # | 
|  | # This program is distributed in the hope that it will be useful, | 
|  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | # GNU General Public License for more details. | 
|  | # | 
|  | # You should have received a copy of the GNU General Public License | 
|  | # along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
|  |  | 
|  | # Test custom MI commands implemented in Python. | 
|  |  | 
|  | load_lib gdb-python.exp | 
|  | load_lib mi-support.exp | 
|  | set MIFLAGS "-i=mi" | 
|  |  | 
|  | gdb_exit | 
|  | if {[mi_gdb_start]} { | 
|  | return | 
|  | } | 
|  |  | 
|  | if {[lsearch -exact [mi_get_features] python] < 0} { | 
|  | unsupported "python support is disabled" | 
|  | return -1 | 
|  | } | 
|  |  | 
|  | standard_testfile | 
|  |  | 
|  | mi_gdb_test "set python print-stack full" \ | 
|  | ".*\\^done" \ | 
|  | "set python print-stack full" | 
|  |  | 
|  | mi_gdb_test "source ${srcdir}/${subdir}/${testfile}.py" \ | 
|  | ".*\\^done" \ | 
|  | "load python file" | 
|  |  | 
|  | mi_gdb_test "python pycmd1('-pycmd')" \ | 
|  | ".*\\^done" \ | 
|  | "define -pycmd MI command" | 
|  |  | 
|  | mi_gdb_test "-pycmd int" \ | 
|  | "\\^done,result=\"42\"" \ | 
|  | "-pycmd int" | 
|  |  | 
|  | mi_gdb_test "-pycmd str" \ | 
|  | "\\^done,result=\"Hello world!\"" \ | 
|  | "-pycmd str" | 
|  |  | 
|  | mi_gdb_test "-pycmd ary" \ | 
|  | "\\^done,result=\\\[\"Hello\",\"42\"\\\]" \ | 
|  | "-pycmd ary" | 
|  |  | 
|  | set re_order1 "\\^done,result={hello=\"world\",times=\"42\"}" | 
|  | set re_order2 "\\^done,result={times=\"42\",hello=\"world\"}" | 
|  | mi_gdb_test "-pycmd dct" \ | 
|  | "($re_order1|$re_order2)" \ | 
|  | "-pycmd dct" | 
|  |  | 
|  | mi_gdb_test "-pycmd bk1" \ | 
|  | "\\^error,msg=\"Error occurred in Python: non-string object used as key: Bad Key\"" \ | 
|  | "-pycmd bk1" | 
|  |  | 
|  | mi_gdb_test "-pycmd bk2" \ | 
|  | "\\^error,msg=\"Error occurred in Python: non-string object used as key: 1\"" \ | 
|  | "-pycmd bk2" | 
|  |  | 
|  | mi_gdb_test "-pycmd bk3" \ | 
|  | [multi_line \ | 
|  | "&\"TypeError.*: __repr__ returned non-string \\(type BadKey\\)..\"" \ | 
|  | "\\^error,msg=\"Error occurred in Python: __repr__ returned non-string \\(type BadKey\\)\""] \ | 
|  | "-pycmd bk3" | 
|  |  | 
|  | mi_gdb_test "-pycmd tpl" \ | 
|  | "\\^done,result=\\\[\"42\",\"Hello\"\\\]" \ | 
|  | "-pycmd tpl" | 
|  |  | 
|  | mi_gdb_test "-pycmd itr" \ | 
|  | "\\^done,result=\\\[\"1\",\"2\",\"3\"\\\]" \ | 
|  | "-pycmd itr" | 
|  |  | 
|  | mi_gdb_test "-pycmd nn1" \ | 
|  | "\\^done" \ | 
|  | "-pycmd nn1" | 
|  |  | 
|  | mi_gdb_test "-pycmd nn2" \ | 
|  | "\\^done,result=\\\[\"None\"\\\]" \ | 
|  | "-pycmd nn2" | 
|  |  | 
|  | mi_gdb_test "-pycmd bogus" \ | 
|  | "\\^error,msg=\"Invalid parameter: bogus\"" \ | 
|  | "-pycmd bogus" | 
|  |  | 
|  | # Check that the top-level result from 'invoke' must be a dictionary. | 
|  | foreach test_name { nd1 nd2 nd3 } { | 
|  | mi_gdb_test "-pycmd ${test_name}" \ | 
|  | "\\^error,msg=\"Error occurred in Python: Result from invoke must be a dictionary\"" | 
|  | } | 
|  |  | 
|  | # Check for invalid strings in the result. | 
|  | foreach test_desc { {ik1 "xxx yyy"} {ik2 "xxx yyy"} {ik3 "xxx\\+yyy"} \ | 
|  | {ik4 "xxx\\.yyy"} {ik5 "123xxxyyy"} } { | 
|  | lassign $test_desc name pattern | 
|  |  | 
|  | mi_gdb_test "-pycmd ${name}" \ | 
|  | "\\^error,msg=\"Error occurred in Python: Invalid key in MI result: ${pattern}\"" | 
|  | } | 
|  |  | 
|  | mi_gdb_test "-pycmd empty_key" \ | 
|  | "\\^error,msg=\"Error occurred in Python: Invalid empty key in MI result\"" | 
|  |  | 
|  | # Check that a dash ('-') can be used in a key name. | 
|  | mi_gdb_test "-pycmd dash-key" \ | 
|  | "\\^done,the-key=\"123\"" | 
|  |  | 
|  | # With this argument the command raises a gdb.GdbError with no message | 
|  | # string.  GDB considers this a bug in the user program, so prints a | 
|  | # backtrace, and a generic error message. | 
|  |  | 
|  | set line1 \ | 
|  | [string_to_regexp {Traceback (most recent call last):\n}] | 
|  | set line2 \ | 
|  | [string cat \ | 
|  | [string_to_regexp {  File \"}] \ | 
|  | "\[^\r\n\]+" \ | 
|  | [string_to_regexp ${testfile}.py] \ | 
|  | [string_to_regexp {\", line }] \ | 
|  | $decimal \ | 
|  | [string_to_regexp {, in invoke\n}]] | 
|  | set line3 \ | 
|  | [string_to_regexp {    raise gdb.GdbError()\n}] | 
|  | set line4 \ | 
|  | [string_to_regexp {gdb.GdbError\n}] | 
|  | set errline \ | 
|  | [string_to_regexp {^error,msg="Error occurred in Python."}] | 
|  |  | 
|  | set start_line \ | 
|  | [string_to_regexp {&"}] | 
|  | set end_line \ | 
|  | [string_to_regexp {"}] | 
|  |  | 
|  | # With python <= 3.12. | 
|  | set re1 \ | 
|  | [multi_line \ | 
|  | $start_line$line1$end_line \ | 
|  | $start_line$line2$end_line \ | 
|  | $start_line$line3$end_line \ | 
|  | $start_line$line4$end_line \ | 
|  | $errline] | 
|  |  | 
|  | # With python >= 3.13. | 
|  | set re2 \ | 
|  | [multi_line \ | 
|  | $start_line$line1$end_line \ | 
|  | $start_line$line2$line3$end_line \ | 
|  | $start_line$line4$end_line \ | 
|  | $errline] | 
|  |  | 
|  | mi_gdb_test "-pycmd exp" ($re1|$re2) | 
|  |  | 
|  | mi_gdb_test "python pycmd2('-pycmd')" \ | 
|  | ".*\\^done" \ | 
|  | "redefine -pycmd MI command from CLI command" | 
|  |  | 
|  | mi_gdb_test "-pycmd str" \ | 
|  | "\\^done,result=\"Ciao!\"" \ | 
|  | "-pycmd str - redefined from CLI" | 
|  |  | 
|  | mi_gdb_test "-pycmd int" \ | 
|  | "\\^error,msg=\"Invalid parameter: int\"" \ | 
|  | "-pycmd int - redefined from CLI" | 
|  |  | 
|  | mi_gdb_test "-pycmd new" \ | 
|  | "\\^done" \ | 
|  | "Define new command -pycmd-new MI command from Python MI command" | 
|  |  | 
|  | mi_gdb_test "-pycmd red" \ | 
|  | "\\^error,msg=\"Command redefined but we failing anyway\"" \ | 
|  | "redefine -pycmd MI command from Python MI command" | 
|  |  | 
|  | mi_gdb_test "-pycmd int" \ | 
|  | "\\^done,result=\"42\"" \ | 
|  | "-pycmd int - redefined from MI" | 
|  |  | 
|  | mi_gdb_test "-pycmd-new int" \ | 
|  | "\\^done,result=\"42\"" \ | 
|  | "-pycmd-new int - defined from MI" | 
|  |  | 
|  | mi_gdb_test "python pycmd1('')" \ | 
|  | ".*&\"ValueError.*: MI command name is empty\\...\".*\\^error,msg=\"Error occurred in Python.*\"" \ | 
|  | "empty MI command name" | 
|  |  | 
|  | mi_gdb_test "python pycmd1('-')" \ | 
|  | [multi_line \ | 
|  | ".*" \ | 
|  | "&\"ValueError.*: MI command name does not start with '-' followed by at least one letter or digit\\...\"" \ | 
|  | "&\"Error occurred in Python.*..\"" \ | 
|  | "\\^error,msg=\"Error occurred in Python.*\""] \ | 
|  | "invalid MI command name" | 
|  |  | 
|  | mi_gdb_test "python pycmd1('-bad-character-@')" \ | 
|  | [multi_line \ | 
|  | ".*" \ | 
|  | "&\"ValueError.*: MI command name contains invalid character: @\\...\"" \ | 
|  | "&\"Error occurred in Python.*..\"" \ | 
|  | "\\^error,msg=\"Error occurred in Python.*\""] \ | 
|  | "invalid character in MI command name" | 
|  |  | 
|  | mi_gdb_test "python cmd=pycmd1('-abc')" \ | 
|  | ".*\\^done" \ | 
|  | "create command -abc, stored in a python variable" | 
|  |  | 
|  | mi_gdb_test "python print(cmd.name)" \ | 
|  | ".*\r\n~\"-abc\\\\n\"\r\n\\^done" \ | 
|  | "print the name of the stored mi command" | 
|  |  | 
|  | mi_gdb_test "python print(cmd.installed)" \ | 
|  | ".*\r\n~\"True\\\\n\"\r\n\\^done" \ | 
|  | "print the installed status of the stored mi command" | 
|  |  | 
|  | mi_gdb_test "-abc str" \ | 
|  | "\\^done,result=\"Hello world!\"" \ | 
|  | "-abc str" | 
|  |  | 
|  | mi_gdb_test "python cmd.installed = False" \ | 
|  | ".*\\^done" \ | 
|  | "uninstall the mi command" | 
|  |  | 
|  | mi_gdb_test "-abc str" \ | 
|  | "\\^error,msg=\"Undefined MI command: abc\",code=\"undefined-command\"" \ | 
|  | "-abc str, but now the command is gone" | 
|  |  | 
|  | mi_gdb_test "python cmd.installed = None" \ | 
|  | ".*\r\n\\^error,msg=\"Error occurred in Python: gdb\\.MICommand\\.installed must be set to a bool, not None\"" \ | 
|  | "re-install the mi command using value None" | 
|  |  | 
|  | mi_gdb_test "python cmd.installed = 1" \ | 
|  | ".*\r\n\\^error,msg=\"Error occurred in Python: gdb\\.MICommand\\.installed must be set to a bool, not int\"" \ | 
|  | "re-install the mi command using an int value" | 
|  |  | 
|  | mi_gdb_test "python print(cmd.installed)" \ | 
|  | [multi_line \ | 
|  | ".*" \ | 
|  | "~\"False\\\\n\"" \ | 
|  | "\\^done"] \ | 
|  | "cmd is still not installed" | 
|  |  | 
|  | mi_gdb_test "python cmd.installed = True" \ | 
|  | ".*\\^done" \ | 
|  | "re-install the mi command" | 
|  |  | 
|  | mi_gdb_test "-abc str" \ | 
|  | "\\^done,result=\"Hello world!\"" \ | 
|  | "-abc str, the command is back again" | 
|  |  | 
|  | mi_gdb_test "python other=pycmd2('-abc')" \ | 
|  | ".*\\^done" \ | 
|  | "create another command called -abc, stored in a separate python variable" | 
|  |  | 
|  | mi_gdb_test "python print(other.installed)" \ | 
|  | ".*\r\n~\"True\\\\n\"\r\n\\^done" \ | 
|  | "print the installed status of the other stored mi command" | 
|  |  | 
|  | mi_gdb_test "python print(cmd.installed)" \ | 
|  | ".*\r\n~\"False\\\\n\"\r\n\\^done" \ | 
|  | "print the installed status of the original stored mi command" | 
|  |  | 
|  | mi_gdb_test "-abc str" \ | 
|  | "\\^done,result=\"Ciao!\"" \ | 
|  | "-abc str, when the other command is in place" | 
|  |  | 
|  | mi_gdb_test "python cmd.installed = True" \ | 
|  | ".*\\^done" \ | 
|  | "re-install the original mi command" | 
|  |  | 
|  | mi_gdb_test "-abc str" \ | 
|  | "\\^done,result=\"Hello world!\"" \ | 
|  | "-abc str, the original command is back again" | 
|  |  | 
|  | mi_gdb_test "python print(other.installed)" \ | 
|  | ".*\r\n~\"False\\\\n\"\r\n\\^done" \ | 
|  | "the other command is now not installed" | 
|  |  | 
|  | mi_gdb_test "python print(cmd.installed)" \ | 
|  | ".*\r\n~\"True\\\\n\"\r\n\\^done" \ | 
|  | "the original command is now installed" | 
|  |  | 
|  | mi_gdb_test "python aa = pycmd3('-aa', 'message one', 'xxx')" \ | 
|  | ".*\\^done" \ | 
|  | "created a new -aa command" | 
|  |  | 
|  | mi_gdb_test "-aa" \ | 
|  | ".*\\^done,xxx={msg=\"message one\"}" \ | 
|  | "call the -aa command" | 
|  |  | 
|  | mi_gdb_test "python aa.__init__('-aa', 'message two', 'yyy')" \ | 
|  | ".*\\^done" \ | 
|  | "reinitialise -aa command with a new message" | 
|  |  | 
|  | mi_gdb_test "-aa" \ | 
|  | ".*\\^done,yyy={msg=\"message two\"}" \ | 
|  | "call the -aa command, get the new message" | 
|  |  | 
|  | mi_gdb_test "python aa.__init__('-bb', 'message three', 'zzz')" \ | 
|  | [multi_line \ | 
|  | ".*" \ | 
|  | "&\"ValueError.*: can't reinitialize object with a different command name..\"" \ | 
|  | "&\"Error occurred in Python.*..\"" \ | 
|  | "\\^error,msg=\"Error occurred in Python.*\""] \ | 
|  | "attempt to reinitialise aa variable to a new command name" | 
|  |  | 
|  | mi_gdb_test "-aa" \ | 
|  | ".*\\^done,yyy={msg=\"message two\"}" \ | 
|  | "check the aa object has not changed after failed initialization" | 
|  |  | 
|  | mi_gdb_test "python aa.installed = False" \ | 
|  | ".*\\^done" \ | 
|  | "uninstall the -aa command" | 
|  |  | 
|  | mi_gdb_test "python aa.__init__('-bb', 'message three', 'zzz')" \ | 
|  | [multi_line \ | 
|  | ".*" \ | 
|  | "&\"ValueError.*: can't reinitialize object with a different command name..\"" \ | 
|  | "&\"Error occurred in Python.*..\"" \ | 
|  | "\\^error,msg=\"Error occurred in Python.*\""] \ | 
|  | "attempt to reinitialise aa variable to a new command name while uninstalled" | 
|  |  | 
|  | mi_gdb_test "python aa.__init__('-aa', 'message three', 'zzz')" \ | 
|  | ".*\\^done" \ | 
|  | "reinitialise -aa command with a new message while uninstalled" | 
|  |  | 
|  | mi_gdb_test "python aa.installed = True" \ | 
|  | ".*\\^done" \ | 
|  | "install the -aa command" | 
|  |  | 
|  | mi_gdb_test "-aa" \ | 
|  | ".*\\^done,zzz={msg=\"message three\"}" \ | 
|  | "call the -aa command looking for message three" | 
|  |  | 
|  | # Try to register a command object that is missing an invoke method. | 
|  | # This is accepted, but will give an error when the user tries to run | 
|  | # the command. | 
|  | mi_gdb_test "python no_invoke('-no-invoke')" ".*\\^done" \ | 
|  | "attempt to register command with no invoke method" | 
|  | mi_gdb_test "-no-invoke" \ | 
|  | [multi_line \ | 
|  | ".*" \ | 
|  | "&\"AttributeError.*: 'no_invoke' object has no attribute 'invoke'..\"" \ | 
|  | "\\^error,msg=\"Error occurred in Python: 'no_invoke' object has no attribute 'invoke'\""] \ | 
|  | "execute -no-invoke command, which is missing the invoke method" | 
|  |  | 
|  | # Register a command, then delete its invoke method.  What is the user thinking!! | 
|  | mi_gdb_test "python setattr(no_invoke, 'invoke', free_invoke)" ".*\\^done" | 
|  | mi_gdb_test "python cmd = no_invoke('-hello')" ".*\\^done" | 
|  | mi_gdb_test "-hello" ".*\\^done,result=\\\[\\\]" \ | 
|  | "execute no_invoke command, while it still has an invoke attribute" | 
|  | mi_gdb_test "python delattr(no_invoke, 'invoke')" ".*\\^done" | 
|  | mi_gdb_test "-hello" \ | 
|  | [multi_line \ | 
|  | ".*" \ | 
|  | "&\"AttributeError.*: 'no_invoke' object has no attribute 'invoke'..\"" \ | 
|  | "\\^error,msg=\"Error occurred in Python: 'no_invoke' object has no attribute 'invoke'\""] \ | 
|  | "execute -hello command, that had its invoke method removed" | 
|  | mi_gdb_test "python cmd.invoke = 'string'" ".*\\^done" | 
|  | mi_gdb_test "-hello" \ | 
|  | [multi_line \ | 
|  | ".*" \ | 
|  | "&\"TypeError.*: 'str' object is not callable..\"" \ | 
|  | "\\^error,msg=\"Error occurred in Python: 'str' object is not callable\""] \ | 
|  | "execute command with invoke set to a string" | 
|  |  | 
|  | # Try to create a new MI command that uses the name of a builtin MI command. | 
|  | mi_gdb_test "python cmd = pycmd2('-data-disassemble')" \ | 
|  | [multi_line \ | 
|  | ".*" \ | 
|  | "&\"RuntimeError.*: unable to add command, name is already in use..\"" \ | 
|  | "&\"Error occurred in Python.*..\"" \ | 
|  | "\\^error,msg=\"Error occurred in Python.*\""] \ | 
|  | "try to register a command that replaces -data-disassemble" | 
|  |  | 
|  |  | 
|  |  | 
|  | mi_gdb_test "python run_exception_tests()" \ | 
|  | [multi_line \ | 
|  | ".*" \ | 
|  | "~\"PASS..\"" \ | 
|  | "\\^done"] |