# Copyright 2017-2019 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 . # This file is part of the gdb testsuite. # Any variable or procedure in the namespace whose name starts with # "_" is private to the module. Do not use these. namespace eval completion { variable bell_re "\\\x07" # List of all quote chars. variable all_quotes_list {"'" "\""} # List of all quote chars, including no-quote at all. variable maybe_quoted_list {"" "'" "\""} variable keyword_list {"if" "task" "thread"} variable explicit_opts_list \ {"-function" "-label" "-line" "-qualified" "-source"} } # Make a regular expression that matches a TAB completion list. proc make_tab_completion_list_re { completion_list } { # readline separates the completion columns that fit on the same # line with whitespace. Since we're testing under "set width # unlimited", all completions will be printed on the same line. # The amount of whitespace depends on the length of the widest # completion. We could compute that here and expect the exact # number of ws characters between each completion match, but to # keep it simple, we accept any number of characters. set ws " +" set completion_list_re "" foreach c $completion_list { append completion_list_re [string_to_regexp $c] append completion_list_re $ws } append completion_list_re $ws return $completion_list_re } # Make a regular expression that matches a "complete" command # completion list. CMD_PREFIX is the command prefix added to each # completion match. proc make_cmd_completion_list_re { cmd_prefix completion_list start_quote_char end_quote_char } { set completion_list_re "" foreach c $completion_list { # The command prefix is included in all completion matches. append completion_list_re [string_to_regexp $cmd_prefix$start_quote_char$c$end_quote_char] append completion_list_re "\r\n" } return $completion_list_re } # Clear the input line. proc clear_input_line { test } { global gdb_prompt send_gdb "\003" gdb_test_multiple "" "$test (clearing input line)" { -re "Quit\r\n$gdb_prompt $" { } } } # Test that completing LINE with TAB completes to nothing. proc test_gdb_complete_tab_none { line } { set line_re [string_to_regexp $line] set test "tab complete \"$line\"" send_gdb "$line\t" gdb_test_multiple "" "$test" { -re "^$line_re$completion::bell_re$" { pass "$test" } } clear_input_line $test } # Test that completing INPUT_LINE with TAB completes to # COMPLETE_LINE_RE. APPEND_CHAR_RE is the character expected to be # appended after EXPECTED_OUTPUT. Normally that's a whitespace, but # in some cases it's some other character, like a colon. proc test_gdb_complete_tab_unique { input_line complete_line_re append_char_re } { set test "tab complete \"$input_line\"" send_gdb "$input_line\t" gdb_test_multiple "" "$test" { -re "^$complete_line_re$append_char_re$" { pass "$test" } } clear_input_line $test } # Test that completing INPUT_LINE with TAB completes to "INPUT_LINE + # ADD_COMPLETED_LINE" and that it displays the completion matches in # COMPLETION_LIST. proc test_gdb_complete_tab_multiple { input_line add_completed_line \ completion_list } { global gdb_prompt set input_line_re [string_to_regexp $input_line] set add_completed_line_re [string_to_regexp $add_completed_line] set expected_re [make_tab_completion_list_re $completion_list] set test "tab complete \"$input_line\"" send_gdb "$input_line\t" gdb_test_multiple "" "$test (first tab)" { -re "^${input_line_re}${completion::bell_re}$add_completed_line_re$" { send_gdb "\t" # If we auto-completed to an ambiguous prefix, we need an # extra tab to show the matches list. if {$add_completed_line != ""} { send_gdb "\t" set maybe_bell ${completion::bell_re} } else { set maybe_bell "" } gdb_test_multiple "" "$test (second tab)" { -re "^${maybe_bell}\r\n$expected_re\r\n$gdb_prompt $input_line_re$add_completed_line_re$" { pass "$test" } } } } clear_input_line $test } # Test that completing LINE with the complete command completes to # nothing. proc test_gdb_complete_cmd_none { line } { gdb_test_no_output "complete $line" "cmd complete \"$line\"" } # Test that completing LINE with the complete command completes to # COMPLETE_LINE_RE. proc test_gdb_complete_cmd_unique { input_line complete_line_re } { global gdb_prompt set cmd "complete $input_line" set cmd_re [string_to_regexp $cmd] set test "cmd complete \"$input_line\"" gdb_test_multiple $cmd $test { -re "^$cmd_re\r\n$complete_line_re\r\n$gdb_prompt $" { pass $test } } } # Test that completing "CMD_PREFIX + COMPLETION_WORD" with the # complete command displays the COMPLETION_LIST completion list. Each # entry in the list should be prefixed by CMD_PREFIX. proc test_gdb_complete_cmd_multiple { cmd_prefix completion_word completion_list {start_quote_char ""} {end_quote_char ""} } { global gdb_prompt set expected_re [make_cmd_completion_list_re $cmd_prefix $completion_list $start_quote_char $end_quote_char] set cmd_re [string_to_regexp "complete $cmd_prefix$completion_word"] set test "cmd complete \"$cmd_prefix$completion_word\"" gdb_test_multiple "complete $cmd_prefix$completion_word" $test { -re "^$cmd_re\r\n$expected_re$gdb_prompt $" { pass $test } } } # Test that completing LINE completes to nothing. proc test_gdb_complete_none { input_line } { test_gdb_complete_tab_none $input_line test_gdb_complete_cmd_none $input_line } # Test that completing INPUT_LINE completes to COMPLETE_LINE_RE. # # APPEND_CHAR is the character expected to be appended after # EXPECTED_OUTPUT when TAB completing. Normally that's a whitespace, # but in some cases it's some other character, like a colon. # # If MAX_COMPLETIONS is true, then we expect the completion to hit the # max-completions limit. Since we're expecting a unique completion # match, this will only be visible in the "complete" command output. # Tab completion will just auto-complete the only match and won't # display a match list. # # Note: usually it's more convenient to pass a literal string instead # of a regular expression (as COMPLETE_LINE_RE). See # test_gdb_complete_unique below. proc test_gdb_complete_unique_re { input_line complete_line_re {append_char " "} {max_completions 0}} { set append_char_re [string_to_regexp $append_char] test_gdb_complete_tab_unique $input_line $complete_line_re $append_char_re # Trim INPUT_LINE and COMPLETE LINE, for the case we're completing # a command with leading whitespace. Leading command whitespace # is discarded by GDB. set input_line [string trimleft $input_line] set expected_output_re [string trimleft $complete_line_re] if {$append_char_re != " "} { append expected_output_re $append_char_re } if {$max_completions} { set max_completion_reached_msg \ "*** List may be truncated, max-completions reached. ***" set input_line_re \ [string_to_regexp $input_line] set max_completion_reached_msg_re \ [string_to_regexp $max_completion_reached_msg] append expected_output_re \ "\r\n$input_line_re $max_completion_reached_msg_re" } test_gdb_complete_cmd_unique $input_line $expected_output_re } # Like TEST_GDB_COMPLETE_UNIQUE_RE, but COMPLETE_LINE is a string, not # a regular expression. proc test_gdb_complete_unique { input_line complete_line {append_char " "} {max_completions 0}} { set complete_line_re [string_to_regexp $complete_line] test_gdb_complete_unique_re $input_line $complete_line_re $append_char $max_completions } # Test that completing "CMD_PREFIX + COMPLETION_WORD" adds # ADD_COMPLETED_LINE to the input line, and that it displays # COMPLETION_LIST as completion match list. COMPLETION_WORD is the # completion word. proc test_gdb_complete_multiple { cmd_prefix completion_word add_completed_line completion_list {start_quote_char ""} {end_quote_char ""}} { test_gdb_complete_tab_multiple "$cmd_prefix$completion_word" $add_completed_line $completion_list test_gdb_complete_cmd_multiple $cmd_prefix $completion_word $completion_list $start_quote_char $end_quote_char } # Test that all the substring prefixes of INPUT from [0..START) to # [0..END) complete to COMPLETION_RE (a regular expression). If END # is ommitted, default to the length of INPUT. proc test_complete_prefix_range_re {input completion_re start {end -1}} { if {$end == -1} { set end [string length $input] } for {set i $start} {$i < $end} {incr i} { set line [string range $input 0 $i] test_gdb_complete_unique_re "$line" $completion_re } } # Test that all the substring prefixes of COMPLETION from [0..START) # to [0..END) complete to COMPLETION. If END is ommitted, default to # the length of COMPLETION. proc test_complete_prefix_range {completion start {end -1}} { set completion_re [string_to_regexp $completion] test_complete_prefix_range_re $completion $completion_re $start $end } # Find NEEDLE in HAYSTACK and return the index _after_ NEEDLE. E.g., # searching for "(" in "foo(int)" returns 4, which would be useful if # you want to find the "(" to try completing "foo(". proc index_after {needle haystack} { set start [string first $needle $haystack] if {$start == -1} { error "could not find \"$needle\" in \"$haystack\"" } return [expr $start + [string length $needle]] } # Create a breakpoint using BREAK_COMMAND, and return the number # of locations found. proc completion::_create_bp {break_command} { global gdb_prompt global decimal hex set found_locations -1 set test "set breakpoint" gdb_test_multiple "$break_command" $test { -re "\\\(\($decimal\) locations\\\)\r\n$gdb_prompt $" { set found_locations "$expect_out(1,string)" } -re "Breakpoint $decimal at $hex: file .*, line .*$gdb_prompt $" { set found_locations 1 } -re "Make breakpoint pending on future shared library load.*y or .n.. $" { send_gdb "n\n" gdb_test_multiple "" "$test (prompt)" { -re "$gdb_prompt $" { } } set found_locations 0 } -re "invalid explicit location argument, \[^\r\n\]*\r\n$gdb_prompt $" { set found_locations 0 } -re "Function \[^\r\n\]* not defined in \[^\r\n\]*\r\n$gdb_prompt $" { set found_locations 0 } } return $found_locations } # Return true if lists A and B have the same elements. Order of # elements does not matter. proc completion::_leq {a b} { return [expr {[lsort $a] eq [lsort $b]}] } # Check that trying to create a breakpoint using BREAK_COMMAND fails. proc check_setting_bp_fails {break_command} { with_test_prefix "\"$break_command\" creates no bp locations" { set found_locations [completion::_create_bp $break_command] gdb_assert {$found_locations == 0} "matches" if {$found_locations != 0} { delete_breakpoints } } } # Check that creating the breakpoint using BREAK_COMMAND finds the # same breakpoint locations as completing BREAK_COMMAND. # COMPLETION_LIST is the expected completion match list. proc check_bp_locations_match_list {break_command completion_list} { global gdb_prompt global hex with_test_prefix "compare \"$break_command\" completion list with bp location list" { set num_locations [completion::_create_bp $break_command] set found_list "" set any "\[^\r\n\]*" gdb_test_multiple "info breakpoint \$bpnum" "info breakpoint" { -re "in \(\[^\r\n\]*\) at " { # A function location. set found_location "$expect_out(1,string)" lappend found_list $found_location exp_continue } -re "breakpoint${any}keep${any}y${any}$hex\[ \t]*\(${any}\)\r\n" { # A label location. set found_location "$expect_out(1,string)" lappend found_list $found_location exp_continue } -re "$gdb_prompt $" { } } gdb_assert {[completion::_leq $found_list $completion_list]} "matches" delete_breakpoints } } # Build linespec and explicit locations out of all the combinations of # SOURCES, FUNCTIONS and LABELS, with all combinations of possible # quoting and whitespace around separators, and run BODY_LINESPEC and # BODY_EXPLICIT in the context of the caller for each combination. A # variable named "location" is set in the callers context with the # currently iterated location. proc foreach_location_functions { sources functions body_linespec body_explicit } { upvar source source upvar function function upvar source_sep source_sep upvar location location foreach source $sources { # Test with and without source quoting. foreach sqc $completion::maybe_quoted_list { if {$source == "" && $sqc != ""} { # Invalid combination. continue } # Test with and without function quoting. foreach fqc $completion::maybe_quoted_list { # Test known and unknown functions. foreach function $functions { # Linespec version. Test with and without spacing # after the source/colon colon separator. foreach source_sep {"" ":" ": "} { # Skip invalid combinations. if {$source == "" && $source_sep != ""} { continue } if {$source != "" && $source_sep == ""} { continue } set location "${sqc}${source}${sqc}${source_sep}${fqc}$function${fqc}" uplevel 1 $body_linespec } # Explicit locations version. if {$source != ""} { set loc_src "-source ${sqc}${source}${sqc} " } else { set loc_src "" } set location "${loc_src}-function ${fqc}$function${fqc}" uplevel 1 $body_explicit } } } } } # Same as foreach_locations_functions, but also iterate over # combinations of labels. proc foreach_location_labels { sources functions labels body_linespec body_explicit } { upvar source source upvar function function upvar label label upvar source_sep source_sep upvar label_sep label_sep upvar location location # Test both with a known source file and without a source file # component. foreach_location_functions \ $sources \ $functions \ { # Linespec version. Test various spacing around the label # colon separator. set saved_location ${location} foreach label_sep {":" " :" ": " " : "} { # Test both known and unknown label. foreach label $labels { set location "${saved_location}${label_sep}$label" uplevel 1 $body_linespec } } } \ { # Explicit locations version. set saved_location ${location} foreach label $labels { set location "${saved_location} -label $label" uplevel 1 $body_explicit } } }