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.
This commit is contained in:
Pedro Alves 2017-11-24 23:41:12 +00:00
parent 0662b6a7c1
commit 8955eb2da3
7 changed files with 1682 additions and 7 deletions

View File

@ -1,3 +1,14 @@
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.
2017-11-24 Joel Brobecker <brobecker@adacore.com>
* gdb.ada/catch_ex.exp, gdb.ada/mi_catch_ex.exp,

View File

@ -0,0 +1,534 @@
# 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.
load_lib completion-support.exp
standard_testfile cpls.cc cpls2.cc cpls-hyphen.cc
if {[prepare_for_testing "failed to prepare" $testfile \
[list $srcfile $srcfile2 $srcfile3] {debug}]} {
return -1
}
# Disable the completion limit for the whole testcase.
gdb_test_no_output "set max-completions unlimited"
# Start of tests.
# Test completion of all parameter prefixes, crossing "(" and ")",
# with and without whitespace.
proc_with_prefix all-param-prefixes {} {
# Test both linespecs and explicit locations.
foreach cmd_prefix {"b" "b -function"} {
set line "$cmd_prefix param_prefixes_test_long(long)"
set start [index_after "test_long" $line]
test_complete_prefix_range $line $start
# Same, but with extra spaces. Note that the original spaces in
# the input line are preserved after completion.
test_gdb_complete_unique \
"$cmd_prefix param_prefixes_test_long(long " \
"$cmd_prefix param_prefixes_test_long(long )"
test_gdb_complete_unique \
"$cmd_prefix param_prefixes_test_long( long " \
"$cmd_prefix param_prefixes_test_long( long )"
test_gdb_complete_unique \
"$cmd_prefix param_prefixes_test_long ( long " \
"$cmd_prefix param_prefixes_test_long ( long )"
# Complete all parameter prefixes between "(i" and "(int*, int&)".
# Note that this exercises completing when the point is at the
# space in "param_prefixes_test_intp_intr(int*, ".
set line "$cmd_prefix param_prefixes_test_intp_intr(int*, int&)"
set start [index_after "intp_intr" $line]
test_complete_prefix_range $line $start
# Similar, but with extra spaces.
test_gdb_complete_unique \
"$cmd_prefix param_prefixes_test_intp_intr ( int* " \
"$cmd_prefix param_prefixes_test_intp_intr ( int* , int&)"
test_gdb_complete_unique \
"$cmd_prefix param_prefixes_test_intp_intr ( int *" \
"$cmd_prefix param_prefixes_test_intp_intr ( int *, int&)"
test_gdb_complete_unique \
"$cmd_prefix param_prefixes_test_intp_intr ( int *, int " \
"$cmd_prefix param_prefixes_test_intp_intr ( int *, int &)"
test_gdb_complete_unique \
"$cmd_prefix param_prefixes_test_intp_intr ( int *, int & " \
"$cmd_prefix param_prefixes_test_intp_intr ( int *, int & )"
}
}
# Test completion of an overloaded function.
proc_with_prefix overload {} {
set completion_list {
"overload_ambiguous_test(int, int)"
"overload_ambiguous_test(int, long)"
"overload_ambiguous_test(long)"
}
foreach cmd_prefix {"b" "b -function"} {
test_gdb_complete_multiple \
"$cmd_prefix " "overload_ambiguous_" "test(" \
$completion_list
check_bp_locations_match_list \
"$cmd_prefix overload_ambiguous_test" \
$completion_list
# Test disambiguating by typing enough to pick the "int" as
# first parameter type. This then tests handling ambiguity in
# the second parameter, which checks that tab completion when
# the point is at the whitespace behaves naturally, by showing
# the remaining matching overloads to the user.
test_gdb_complete_multiple \
"$cmd_prefix " "overload_ambiguous_test(i" "nt, " {
"overload_ambiguous_test(int, int)"
"overload_ambiguous_test(int, long)"
}
# Add a few more characters to make the completion
# unambiguous.
test_gdb_complete_unique \
"$cmd_prefix overload_ambiguous_test(int, i" \
"$cmd_prefix overload_ambiguous_test(int, int)"
check_bp_locations_match_list \
"$cmd_prefix overload_ambiguous_test(int, int)" {
"overload_ambiguous_test(int, int)"
}
}
}
# Test that when the function is unambiguous, linespec completion
# appends the end quote char automatically, both ' and ".
proc_with_prefix append-end-quote-char-when-unambiguous {} {
foreach cmd_prefix {"b" "b -function"} {
foreach qc $completion::all_quotes_list {
set linespec "${qc}not_overloaded_fn()${qc}"
foreach cmd [list "$cmd_prefix ${qc}not_overloaded_fn()" \
"$cmd_prefix ${qc}not_overloaded_fn" \
"$cmd_prefix ${qc}not_overloaded_"] {
test_gdb_complete_unique $cmd "$cmd_prefix $linespec"
}
check_bp_locations_match_list \
"$cmd_prefix $linespec" {"not_overloaded_fn()"}
}
}
}
# Test completing symbols of source files.
proc_with_prefix in-source-file-unconstrained {} {
# First test that unconstrained matching picks up functions from
# multiple files.
test_gdb_complete_multiple "b " "file_constrained_test" "_cpls" {
"file_constrained_test_cpls2_function(int)"
"file_constrained_test_cpls_function(int)"
}
check_setting_bp_fails "b file_constrained_test_cpls"
}
# Test an unambiguous completion that would be ambiguous if it weren't
# for the source file component, due to
# "file_constrained_test_cpls_function" in cpls.cc. Test with
# different components quoted, and with whitespace before the function
# name.
proc_with_prefix in-source-file-unambiguous {} {
foreach sqc $completion::maybe_quoted_list {
foreach fqc $completion::maybe_quoted_list {
# Linespec.
foreach sep {":" ": "} {
set linespec \
"${sqc}cpls2.cc${sqc}${sep}${fqc}file_constrained_test_cpls2_function(int)${fqc}"
set complete_line "b $linespec"
set start [index_after "constrained_test" $complete_line]
set input_line [string range $complete_line 0 $start]
test_gdb_complete_unique $input_line ${complete_line}
check_bp_locations_match_list "b $linespec" {
"file_constrained_test_cpls2_function(int)"
}
}
# Explicit location.
set source_opt "-source ${sqc}cpls2.cc${sqc}"
set function_opt "-function ${fqc}file_constrained_test_cpls2_function(int)${fqc}"
set complete_line "b $source_opt $function_opt"
set start [index_after "cpls2_functio" $complete_line]
set input_line [string range $complete_line 0 $start]
test_gdb_complete_unique $input_line ${complete_line}
check_bp_locations_match_list "$complete_line" {
"file_constrained_test_cpls2_function(int)"
}
}
}
}
# Test an ambiguous completion constrained by a source file. Test
# with different components quoted, and with whitespace before the
# function name.
proc_with_prefix in-source-file-ambiguous {} {
foreach sqc $completion::maybe_quoted_list {
foreach fqc $completion::maybe_quoted_list {
# Linespec.
foreach sep {":" ": "} {
set cmd_prefix "b ${sqc}cpls2.cc${sqc}${sep}"
test_gdb_complete_multiple "${cmd_prefix}" ${fqc} "" {
"another_file_constrained_test_cpls2_function(int)"
"file_constrained_test_cpls2_function(int)"
} ${fqc} ${fqc}
}
# Explicit location.
test_gdb_complete_multiple \
"b -source ${sqc}cpls2.cc${sqc} -function " ${fqc} "" {
"another_file_constrained_test_cpls2_function(int)"
"file_constrained_test_cpls2_function(int)"
} ${fqc} ${fqc}
}
}
}
# Check that completing a file name in a linespec auto-appends a colon
# instead of a whitespace character.
proc_with_prefix source-complete-appends-colon {} {
# Test with quotes to make sure the end quote char is put at the
# right place.
foreach qc $completion::maybe_quoted_list {
test_gdb_complete_unique \
"b ${qc}cpls2." \
"b ${qc}cpls2.cc${qc}" ":"
test_gdb_complete_unique \
"b ${qc}cpls2.c" \
"b ${qc}cpls2.cc${qc}" ":"
test_gdb_complete_unique \
"b ${qc}cpls2.cc" \
"b ${qc}cpls2.cc${qc}" ":"
# Same, but with a filename with an hyphen (which is normally
# a language word break char).
test_gdb_complete_unique \
"b ${qc}cpls-" \
"b ${qc}cpls-hyphen.cc${qc}" ":"
test_gdb_complete_unique \
"b ${qc}cpls-hyphen" \
"b ${qc}cpls-hyphen.cc${qc}" ":"
}
# Test the same, but with the name of a nonexisting file.
# Cursor at the end of the string.
test_gdb_complete_none "b nonexistingfilename.cc"
# Cursor past the end of the string.
test_gdb_complete_multiple "b nonexistingfilename.cc " "" "" \
$completion::keyword_list
foreach qc $completion::all_quotes_list {
# Unterminated quote.
test_gdb_complete_none "b ${qc}nonexistingfilename.cc"
test_gdb_complete_none "b ${qc}nonexistingfilename.cc "
# Terminated quote, cursor at the quote.
test_gdb_complete_unique \
"b ${qc}nonexistingfilename.cc${qc}" \
"b ${qc}nonexistingfilename.cc${qc}"
# Terminated quote, cursor past the quote.
test_gdb_complete_multiple \
"b ${qc}nonexistingfilename.cc${qc} " "" "" \
$completion::keyword_list
}
}
####################################################################
# Test that a colon at the end of the linespec is understood as an
# incomplete scope operator (incomplete-scope-colon), instead of a
# source/function separator.
proc_with_prefix incomplete-scope-colon {} {
# Helper for the loop below to simplify it. Tests completion of
# the range defined by the RANGE_SS found in the constructed line.
#
# E.g., with:
#
# source="source.cc"
# fqc="'"
# prototype="ns::function()"
# range_ss="s::f"
#
# we'd try completing with the cursor set in each of the
# underlined range's positions of:
#
# b source.cc:'ns::function()'"
# ^^^^
#
# Also test that setting a breakpoint at the constructed line
# finds the same breakpoint location as completion does.
#
proc incomplete_scope_colon_helper {prototype range_ss {skip_check_bp 0}} {
foreach source {"" "cpls.cc"} {
# 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 {
if {$source == ""} {
set linespec_source ""
set explicit_source ""
} else {
set linespec_source "${sqc}${source}${sqc}:"
set explicit_source "-source ${sqc}${source}${sqc}"
}
# Even though this use case is trickier with
# linespecs due to the ":" as separator, test both
# linespecs and explicit locations for
# completeness.
foreach location [list \
"${linespec_source}${fqc}$prototype${fqc}" \
"${explicit_source} -function ${fqc}$prototype${fqc}"] {
set complete_line "b $location"
set start [string first $range_ss $complete_line]
set end [expr ($start + [string length $range_ss])]
test_complete_prefix_range $complete_line $start $end
if {!$skip_check_bp} {
check_bp_locations_match_list "b $location" [list "$prototype"]
}
}
}
}
}
}
incomplete_scope_colon_helper \
"struct_incomplete_scope_colon_test::incomplete_scope_colon_test()" \
"t::i"
incomplete_scope_colon_helper \
"ns_incomplete_scope_colon_test::incomplete_scope_colon_test()" \
"t::i"
# Test completing around both "::"s.
foreach range_ss {"t::s" "t::i"} skip_check_bp {0 1} {
incomplete_scope_colon_helper \
"ns2_incomplete_scope_colon_test::struct_in_ns2_incomplete_scope_colon_test::incomplete_scope_colon_test()" \
$range_ss $skip_check_bp
}
}
# Basic test for completing "operator<". More extensive C++ operator
# tests in cpls-op.exp.
proc_with_prefix operator< {} {
# Complete all prefixes between "oper" and the whole prototype.
set function "operator<(foo_enum, foo_enum)"
foreach cmd_prefix {"b" "b -function"} {
set line "$cmd_prefix $function"
set start [index_after "oper" $line]
test_complete_prefix_range $line $start
}
# There's a label in the function; try completing it. (Exhaustive
# label completion tests further below.)
foreach location [list \
"$function:label1" \
"-function $function -label label1"] {
set cmd "b $location"
set input_line [string range $cmd 0 [expr [string length $cmd] - 3]]
test_gdb_complete_unique $input_line $cmd
test_gdb_complete_unique $cmd $cmd
check_bp_locations_match_list $cmd [list "$location"]
}
}
# Test completion of function labels.
proc_with_prefix function-labels {} {
# Test with and without a source file component.
foreach_location_functions \
{ "" "cpls.cc" } \
{ "function_with_labels(int)" } \
{
# Linespec version. Test various spacing around the label
# colon separator.
foreach label_sep {":" " :" ": " " : "} {
set linespec "${location}${label_sep}"
test_gdb_complete_multiple "b $linespec" "l" "abel" {
"label1"
"label2"
}
check_setting_bp_fails "b ${linespec}label"
set tsep [string trim ${source_sep}]
check_bp_locations_match_list \
"b ${linespec}label1" [list "${source}${tsep}${function}:label1"]
check_bp_locations_match_list \
"b ${linespec}label2" [list "${source}${tsep}${function}:label2"]
}
} \
{
# Explicit locations version.
append location " -label"
test_gdb_complete_multiple "b $location " "l" "abel" {
"label1"
"label2"
}
check_setting_bp_fails "b $location label"
if {$source != ""} {
set bp_loc_src "-source ${source} "
} else {
set bp_loc_src ""
}
check_bp_locations_match_list \
"b ${location} label1" [list "${bp_loc_src}-function $function -label label1"]
check_bp_locations_match_list \
"b ${location} label2" [list "${bp_loc_src}-function $function -label label2"]
}
}
# Test that completion after a function name offers keyword
# (if/task/thread) matches in linespec mode, and also the explicit
# location options in explicit locations mode.
proc_with_prefix keywords-after-function {} {
set explicit_list \
[concat $completion::explicit_opts_list $completion::keyword_list]
# Test without a source file, with a known source file, and with
# and unknown source file.
# Test a known and an unknown function.
foreach_location_functions \
{ "" "cpls.cc" "unknown_file.cc" } \
{ "function_with_labels(int)" "unknown_function(int)" } \
{
# Linespec version.
test_gdb_complete_multiple "b ${location} " "" "" \
$completion::keyword_list
} \
{
# Explicit locations version.
test_gdb_complete_multiple "b ${location} " "" "" \
$explicit_list
}
}
# Same, but after a label.
proc_with_prefix keywords-after-label {} {
set explicit_list \
[concat $completion::explicit_opts_list $completion::keyword_list]
foreach_location_labels \
{ "" "cpls.cc" } \
{ "function_with_labels(int)" "unknown_function(int)" } \
{ "label1" "non_existing_label" } \
{
# Linespec version.
test_gdb_complete_multiple "b ${location} " "" "" \
$completion::keyword_list
} \
{
# Explicit locations version.
test_gdb_complete_multiple "b ${location} " "" "" \
$explicit_list
}
}
# Similar, but after an unknown file, and in linespec mode only.
proc_with_prefix keywords-after-unknown-file {} {
# Test with and without quoting.
foreach qc $completion::maybe_quoted_list {
set line "b ${qc}unknown_file.cc${qc}: "
test_gdb_complete_multiple $line "" "" $completion::keyword_list
}
}
# Test that linespec / function completion does not match data
# symbols, only functions/methods.
proc_with_prefix no-data-symbols {} {
foreach cmd_prefix {"b" "b -function"} {
test_gdb_complete_unique "$cmd_prefix code_" "$cmd_prefix code_function()"
}
}
# After "if", we expect an expression, which has a different completer
# that matches data symbols as well. Check that that works.
proc_with_prefix if-expression {} {
foreach cmd_prefix {"b" "b -function"} {
test_gdb_complete_multiple "$cmd_prefix function() if " "code_" "" {
"code_data"
"code_function()"
}
test_gdb_complete_unique \
"$cmd_prefix function() if code_data + another_da" \
"$cmd_prefix function() if code_data + another_data"
test_gdb_complete_unique \
"$cmd_prefix non_existing_function() if code_data + another_da" \
"$cmd_prefix non_existing_function() if code_data + another_data"
# FIXME: For now, thread and task also use the expression
# completer.
test_gdb_complete_unique \
"$cmd_prefix function() thread code_data + another_da" \
"$cmd_prefix function() thread code_data + another_data"
test_gdb_complete_unique \
"$cmd_prefix function() task code_data + another_da" \
"$cmd_prefix function() task code_data + another_data"
}
}
# The testcase driver. Calls all test procedures.
proc test_driver {} {
all-param-prefixes
overload
append-end-quote-char-when-unambiguous
in-source-file-unconstrained
in-source-file-unambiguous
in-source-file-ambiguous
source-complete-appends-colon
incomplete-scope-colon
operator<
function-labels
keywords-after-function
keywords-after-label
keywords-after-unknown-file
no-data-symbols
if-expression
}
test_driver

View File

@ -0,0 +1,33 @@
/* This testcase is part of GDB, the GNU debugger.
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/>. */
/* A function in a file whose name has an hyphen. */
int
ns_hyphen_function (int i)
{
if (i > 0)
{
label1:
return i + 20;
}
else
{
label2:
return i + 10;
}
}

View File

@ -0,0 +1,386 @@
/* This testcase is part of GDB, the GNU debugger.
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/>. */
/* Code for the all-param-prefixes test. */
void
param_prefixes_test_long (long)
{}
void
param_prefixes_test_intp_intr (int *, int&)
{}
/* Code for the overload test. */
void
overload_ambiguous_test (long)
{}
void
overload_ambiguous_test (int, int)
{}
void
overload_ambiguous_test (int, long)
{}
/* Code for the overload-2 test. */
/* Generate functions/methods all with the same name, in different
scopes, but all with different parameters. */
struct overload2_arg1 {};
struct overload2_arg2 {};
struct overload2_arg3 {};
struct overload2_arg4 {};
struct overload2_arg5 {};
struct overload2_arg6 {};
struct overload2_arg7 {};
struct overload2_arg8 {};
struct overload2_arg9 {};
struct overload2_arga {};
#define GEN_OVERLOAD2_FUNCTIONS(ARG1, ARG2) \
void \
overload2_function (ARG1) \
{} \
\
struct struct_overload2_test \
{ \
void overload2_function (ARG2); \
}; \
\
void \
struct_overload2_test::overload2_function (ARG2) \
{}
/* In the global namespace. */
GEN_OVERLOAD2_FUNCTIONS( overload2_arg1, overload2_arg2)
namespace
{
/* In an anonymous namespace. */
GEN_OVERLOAD2_FUNCTIONS (overload2_arg3, overload2_arg4)
}
namespace ns_overload2_test
{
/* In a namespace. */
GEN_OVERLOAD2_FUNCTIONS (overload2_arg5, overload2_arg6)
namespace
{
/* In a nested anonymous namespace. */
GEN_OVERLOAD2_FUNCTIONS (overload2_arg7, overload2_arg8)
namespace ns_overload2_test
{
/* In a nested namespace. */
GEN_OVERLOAD2_FUNCTIONS (overload2_arg9, overload2_arga)
}
}
}
/* Code for the overload-3 test. */
#define GEN_OVERLOAD3_FUNCTIONS(ARG1, ARG2) \
void \
overload3_function (ARG1) \
{} \
void \
overload3_function (ARG2) \
{} \
\
struct struct_overload3_test \
{ \
void overload3_function (ARG1); \
void overload3_function (ARG2); \
}; \
\
void \
struct_overload3_test::overload3_function (ARG1) \
{} \
void \
struct_overload3_test::overload3_function (ARG2) \
{}
/* In the global namespace. */
GEN_OVERLOAD3_FUNCTIONS (int, long)
namespace
{
/* In an anonymous namespace. */
GEN_OVERLOAD3_FUNCTIONS (int, long)
}
namespace ns_overload3_test
{
/* In a namespace. */
GEN_OVERLOAD3_FUNCTIONS (int, long)
namespace
{
/* In a nested anonymous namespace. */
GEN_OVERLOAD3_FUNCTIONS (int, long)
namespace ns_overload3_test
{
/* In a nested namespace. */
GEN_OVERLOAD3_FUNCTIONS (int, long)
}
}
}
/* Code for the template-overload tests. */
template <typename T>
struct template_struct
{
T template_overload_fn (T);
};
template <typename T>
T template_struct<T>::template_overload_fn (T t)
{
return t;
}
template_struct<int> template_struct_int;
template_struct<long> template_struct_long;
/* Code for the template2-ret-type tests. */
template <typename T>
struct template2_ret_type {};
template <typename T>
struct template2_struct
{
template <typename T2, typename T3>
T template2_fn (T = T (), T2 t2 = T2 (), T3 t3 = T3 ());
};
template <typename T>
template <typename T2, typename T3>
T template2_struct<T>::template2_fn (T t, T2 t2, T3 t3)
{
return T ();
}
template2_struct<template2_ret_type<int> > template2_struct_inst;
/* Code for the const-overload tests. */
struct struct_with_const_overload
{
void const_overload_fn ();
void const_overload_fn () const;
};
void
struct_with_const_overload::const_overload_fn ()
{}
void
struct_with_const_overload::const_overload_fn () const
{}
void
not_overloaded_fn ()
{}
/* Code for the incomplete-scope-colon tests. */
struct struct_incomplete_scope_colon_test
{
void incomplete_scope_colon_test ();
};
void
struct_incomplete_scope_colon_test::incomplete_scope_colon_test ()
{}
namespace ns_incomplete_scope_colon_test
{
void incomplete_scope_colon_test () {}
}
namespace ns2_incomplete_scope_colon_test
{
struct struct_in_ns2_incomplete_scope_colon_test
{
void incomplete_scope_colon_test ();
};
void
struct_in_ns2_incomplete_scope_colon_test::incomplete_scope_colon_test ()
{}
}
/* Code for the anon-ns tests. */
namespace
{
void anon_ns_function ()
{}
struct anon_ns_struct
{
void anon_ns_function ();
};
void
anon_ns_struct::anon_ns_function ()
{}
}
namespace the_anon_ns_wrapper_ns
{
namespace
{
void anon_ns_function ()
{}
struct anon_ns_struct
{
void anon_ns_function ();
};
void
anon_ns_struct::anon_ns_function ()
{}
}
} /* the_anon_ns_wrapper_ns */
/* Code for the global-ns-scope-op tests. */
void global_ns_scope_op_function ()
{
}
/* Add a function with the same name to a namespace. We want to test
that "b ::global_ns_function" does NOT select it. */
namespace the_global_ns_scope_op_ns
{
void global_ns_scope_op_function ()
{
}
}
/* Code for the ambiguous-prefix tests. */
/* Create a few functions/methods with the same "ambiguous_prefix_"
prefix. They in different scopes, but "b ambiguous_prefix_<tab>"
should list them all, and figure out the LCD is
ambiguous_prefix_. */
void ambiguous_prefix_global_func ()
{
}
namespace the_ambiguous_prefix_ns
{
void ambiguous_prefix_ns_func ()
{
}
}
struct the_ambiguous_prefix_struct
{
void ambiguous_prefix_method ();
};
void
the_ambiguous_prefix_struct::ambiguous_prefix_method ()
{
}
/* Code for the function-labels test. */
int
function_with_labels (int i)
{
if (i > 0)
{
label1:
return i + 20;
}
else
{
label2:
return i + 10;
}
}
/* Code for the no-data-symbols and if-expression tests. */
int code_data = 0;
int another_data = 0;
/* A function that has a same "code" prefix as the global above. We
want to ensure that completing on "b code" doesn't offer the data
symbol. */
void
code_function ()
{
}
/* Code for the operator< tests. */
enum foo_enum
{
foo_value
};
bool operator<(foo_enum lhs, foo_enum rhs)
{
label1:
return false;
}
/* Code for the in-source-file-unconstrained /
in-source-file-ambiguous tests. */
int
file_constrained_test_cpls_function (int i)
{
if (i > 0)
{
label1:
return i + 20;
}
else
{
label2:
return i + 10;
}
}
int
main ()
{
template2_struct_inst.template2_fn<int, int> ();
template_struct_int.template_overload_fn(0);
template_struct_long.template_overload_fn(0);
return 0;
}

View File

@ -0,0 +1,46 @@
/* This testcase is part of GDB, the GNU debugger.
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/>. */
int
file_constrained_test_cpls2_function (int i)
{
if (i > 0)
{
label1:
return i + 20;
}
else
{
label2:
return i + 10;
}
}
int
another_file_constrained_test_cpls2_function (int i)
{
if (i > 0)
{
label1:
return i + 20;
}
else
{
label2:
return i + 10;
}
}

View File

@ -15,6 +15,8 @@
# Tests for explicit locations
load_lib completion-support.exp
standard_testfile explicit.c explicit2.c 3explicit.c
set exefile $testfile
@ -222,13 +224,14 @@ namespace eval $testfile {
}
}
set tst "complete unique file name"
send_gdb "break -source 3ex\t"
gdb_test_multiple "" $tst {
-re "break -source 3explicit.c " {
send_gdb "\n"
gdb_test "" \
{Source filename requires function, label, or line offset.} $tst
with_test_prefix "complete unique file name" {
foreach qc $completion::maybe_quoted_list {
set cmd "break -source ${qc}3explicit.c${qc}"
test_gdb_complete_unique \
"break -source ${qc}3ex" \
$cmd
gdb_test $cmd \
{Source filename requires function, label, or line offset.}
}
}
@ -326,10 +329,201 @@ namespace eval $testfile {
}
}
with_test_prefix "complete unique label name" {
foreach qc $completion::maybe_quoted_list {
test_gdb_complete_unique \
"break -function myfunction -label ${qc}to" \
"break -function myfunction -label ${qc}top${qc}"
}
}
with_test_prefix "complete unique label name with source file" {
test_gdb_complete_unique \
"break -source explicit.c -function myfunction -label to" \
"break -source explicit.c -function myfunction -label top"
}
with_test_prefix "complete unique label name reversed" {
test_gdb_complete_multiple "b -label top -function " "myfunction" "" {
"myfunction"
"myfunction2"
"myfunction3"
"myfunction4"
}
}
with_test_prefix "complete non-unique label name" {
test_gdb_complete_multiple "b -function myfunction -label " "" "" {
"done"
"top"
}
}
# The program is stopped at myfunction, so gdb is able to
# infer the label's function.
with_test_prefix "complete label name with no function" {
test_gdb_complete_unique \
"break -label to" \
"break -label top"
check_bp_locations_match_list \
"break -label top" {
"-function myfunction -label top"
}
}
# See above.
with_test_prefix "complete label name with source file but no function" {
test_gdb_complete_unique \
"break -source explicit.c -label to" \
"break -source explicit.c -label top"
check_bp_locations_match_list \
"break -source explicit.c -label top" {
"-source explicit.c -function myfunction -label top"
}
}
with_test_prefix "complete label name with wrong source file" {
test_gdb_complete_none \
"break -source explicit2.c -function myfunction -label to"
check_setting_bp_fails \
"break -source explicit2.c -function myfunction -label top"
}
# Get rid of symbols from shared libraries, otherwise
# "b -source thr<tab>" could find some system library's
# source.
gdb_test_no_output "nosharedlibrary"
# Test that after a seemingly finished option argument,
# completion matches both the explicit location options and
# the linespec keywords.
set completions_list {
"-function"
"-label"
"-line"
"-source"
"if"
"task"
"thread"
}
foreach what { "-function" "-label" "-line" "-source" } {
with_test_prefix "complete after $what" {
if {$what != "-line"} {
set w "$what argument "
test_gdb_complete_multiple \
"b $w" "" "" $completions_list
test_gdb_complete_unique \
"b $w thr" \
"b $w thread"
test_gdb_complete_unique \
"b $w -fun" \
"b $w -function"
} else {
# After -line, we expect a number / offset.
foreach line {"10" "+10" "-10"} {
set w "-line $line"
test_gdb_complete_multiple \
"b $w " "" "" $completions_list
test_gdb_complete_unique \
"b $w thr" \
"b $w thread"
test_gdb_complete_unique \
"b $w -fun" \
"b $w -function"
}
# With an invalid -line argument, we don't get any
# completions.
test_gdb_complete_none "b -line argument "
}
# Don't complete a linespec keyword ("thread") or
# another option name when expecting an option
# argument.
test_gdb_complete_none "b $what thr"
test_gdb_complete_none "b $what -fun"
}
}
# Tests that ensure that after "if" we complete on expressions
# are in cpcompletion.exp.
# Disable the completion limit for the rest of the testcase.
gdb_test_no_output "set max-completions unlimited"
# Get rid of symbols from shared libraries, otherwise the
# completions match list for "break <tab>" is huge and makes
# the test below quite long while the gdb_test_multiple loop
# below consumes the matches. Not doing this added ~20
# seconds at the time of writing. (Actually, already done above.)
# gdb_test_no_output "nosharedlibrary"
# Test completion with no input argument. We should see all
# the options, plus all the functions. To keep it simple, as
# proxy, we check for presence of one explicit location
# option, one probe location, and one function.
set saw_opt_function 0
set saw_opt_probe_stap 0
set saw_function 0
set tst "complete with no arguments"
send_gdb "break \t"
gdb_test_multiple "" $tst {
"break \\\x07" {
send_gdb "\t\t"
gdb_test_multiple "" $tst {
"Display all" {
send_gdb "y"
exp_continue
}
-re "-function" {
set saw_opt_function 1
exp_continue
}
-re "-probe-stap" {
set saw_opt_probe_stap 1
exp_continue
}
-re "myfunction4" {
set saw_function 1
exp_continue
}
-re "\r\n$gdb_prompt " {
gdb_assert {$saw_opt_function && $saw_opt_probe_stap && $saw_function} $tst
}
-re " " {
exp_continue
}
}
}
}
clear_input_line $tst
# NOTE: We don't bother testing more elaborate combinations of options,
# such as "-func main -sour 3ex\t" (main is defined in explicit.c).
# The completer cannot handle these yet.
# The following completion tests require having no symbols
# loaded.
gdb_exit
gdb_start
# The match list you get when you complete with no options
# specified at all.
set completion_list {
"-function"
"-label"
"-line"
"-probe"
"-probe-dtrace"
"-probe-stap"
"-source"
}
with_test_prefix "complete with no arguments and no symbols" {
test_gdb_complete_multiple "b " "" "-" $completion_list
test_gdb_complete_multiple "b " "-" "" $completion_list
}
}
# End of completion tests.

View File

@ -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
}
}
}