diff options
author | Pedro Alves <palves@redhat.com> | 2017-11-24 23:41:12 +0000 |
---|---|---|
committer | Pedro Alves <palves@redhat.com> | 2017-11-24 23:41:12 +0000 |
commit | 8955eb2da31d78690c762748fab3a16832ef1f81 (patch) | |
tree | e03fa77940af9eba7e83e8111eb960c599a8d017 /gdb/testsuite/lib/completion-support.exp | |
parent | 0662b6a7c1b3b04a4ca31a09af703c91c7aa9646 (diff) | |
download | binutils-gdb-8955eb2da31d78690c762748fab3a16832ef1f81.tar.gz |
Comprehensive C++ linespec/completer tests
Exercises all sorts of aspects fixed by previous patches, going back a
few months.
- Exercises label completion, linespecs and explicit locations.
- Exercises both quoting vs non-quoting, source filenames, function
names, labels, with both linespecs and explicit locations.
- Tests corner cases around not-quoting function names, and
whitespace and/and completing inside a parameter or template
argument list, anonymous namespace awareness, etc.
E.g.,
"break foo<[TAB]" -> "break foo<int>()"
"break bar ( int[TAB]" -> "break bar ( int)
"break ( anon" -> "break ( anonymous namespace)::func()"
"b cfunc() [tab]" -> "b cfunc() const"
"b rettype templfunc[tab]" -> "b rettype templfunc<bar>()"
... and others.
- Tests the "b source.c[TAB] -> b source.cc:" feature. I.e., colon
auto-appending.
- Exercises corner cases around C++ "operator<" / "operator<<".
(Much more extensive C++ operator completion/linespec handling in a
separate patch.)
- Exercises both tab completion and "complete" command completion,
using routines that handle it automatically, to ensure no test
forgets either mode.
- Many of the completion tests test completion at at prefix of a
given tricky name, to make sure all corner cases are covered.
E.g., completing before, at and after ":", "(", "<".
- Exercises "keyword" completion. I.e., "b function() [TAB]"
displaying "if task thread" as completion match list. Likewise for
display explicit location options matches at the appropriate
points.
- Ensures that the completer finds the same breakpoint locations that
setting a breakpoint finds.
- Tests that linespec/location completion doesn't find data symbols.
- Tests that expression completion still kicks in after a
linespec/location keyword. I.e., this:
"b function () if global1 + global[TAB]"
knows that after "if", you're completing on an expression, and thus
breaks words after "if" as an expression and matches on "global" as
a data symbol.
- Adds common routines to help with all the above, to be used by
multiple completion and linespec/location test cases.
- More...
Grows the gdb.linespec/ tests like this:
-# of expected passes 573
+# of expected passes 3464
gdb/testsuite/ChangeLog:
2017-11-24 Pedro Alves <palves@redhat.com>
* gdb.linespec/cpcompletion.exp: New file.
* gdb.linespec/cpls-hyphen.cc: New file.
* gdb.linespec/cpls.cc: New file.
* gdb.linespec/cpls2.cc: New file.
* gdb.linespec/explicit.exp: Load completion-support.exp. Adjust
test to use test_gdb_complete_unique. Add label completion,
keyword completion and explicit location completion tests.
* lib/completion-support.exp: New file.
Diffstat (limited to 'gdb/testsuite/lib/completion-support.exp')
-rw-r--r-- | gdb/testsuite/lib/completion-support.exp | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/gdb/testsuite/lib/completion-support.exp b/gdb/testsuite/lib/completion-support.exp new file mode 100644 index 00000000000..70e6aec895b --- /dev/null +++ b/gdb/testsuite/lib/completion-support.exp @@ -0,0 +1,471 @@ +# Copyright 2017 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/>. + +# 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" "-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" + } + gdb_test_multiple "" "$test (second tab)" { + -re "$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 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}} { + if {$end == -1} { + set end [string length $completion] + } + + for {set i $start} {$i < $end} {incr i} { + set line [string range $completion 0 $i] + test_gdb_complete_unique "$line" "$completion" + } +} + +# 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 + } + } +} |