[binutils-gdb] Add quotemeta to gdb test suite

Tom Tromey tromey@sourceware.org
Sat Jan 31 18:36:13 GMT 2026


https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=0d0ed555bff2ad5d8e08e14310bf0d66e37177d4

commit 0d0ed555bff2ad5d8e08e14310bf0d66e37177d4
Author: Tom Tromey <tom@tromey.com>
Date:   Sat Oct 18 12:25:04 2025 -0600

    Add quotemeta to gdb test suite
    
    AdaCore has an internal test suite written in Python.  One nice
    facility it has is called "quotemeta", and it is an alternative to
    using regular expressions to match gdb output.
    
    This patch adds a similar library, written in Tcl, to the gdb test
    suite.  The idea here is to simplify certain kinds of checks and to
    avoid the painful combination of both Tcl and regexp quoting.
    
    There is a comment in the code that explains quotemeta in detail.  If
    you've used the AdaCore test suite, note that this implementation has
    some extensions, namely the @{...} forms.  (I do wonder here if <...>
    would be a better choice, to avoid Tcl issues with braces.)
    
    I've converted part of a Rust test case to demonstrate the effect.
    The most dramatic example is this change:
    
    -gdb_test "print g" " = \\(\\*mut \\\[u8; 6\\\]\\) $hex b\"hi bob\""
    +gdb_test "print g" [quotemeta {$@DECIMAL = (*mut [u8; 6]) @HEX b"hi bob"}]
    
    At least for me the latter is much easier to understand; and also it
    doesn't cheat on the history part of the output, because it's less
    painful to do this properly now.
    
    Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33551
    Reviewed-By: Tom de Vries <tdevries@suse.de>

Diff:
---
 gdb/testsuite/gdb.rust/simple.exp         |  24 +++---
 gdb/testsuite/gdb.testsuite/quotemeta.exp |  46 ++++++++++
 gdb/testsuite/lib/gdb.exp                 |   1 +
 gdb/testsuite/lib/quotemeta.exp           | 137 ++++++++++++++++++++++++++++++
 4 files changed, 196 insertions(+), 12 deletions(-)

diff --git a/gdb/testsuite/gdb.rust/simple.exp b/gdb/testsuite/gdb.rust/simple.exp
index eb2c5c404e5..1256d655eec 100644
--- a/gdb/testsuite/gdb.rust/simple.exp
+++ b/gdb/testsuite/gdb.rust/simple.exp
@@ -30,20 +30,20 @@ if {![runto ${srcfile}:$line]} {
     return
 }
 
-gdb_test "print a" " = \\(\\)"
-gdb_test "ptype a" " = \\(\\)"
-gdb_test "print sizeof(a)" " = 0"
+gdb_test "print a" [quotemeta "$@DECIMAL = ()"]
+gdb_test "ptype a" [quotemeta "type = ()"]
+gdb_test "print sizeof(a)" [quotemeta "$@DECIMAL = 0"]
 
-gdb_test "print b" " = \\\[\\\]"
-gdb_test "ptype b" " = \\\[i32; 0\\\]"
-gdb_test "print *(&b as *const \[i32; 0\])" " = \\\[\\\]"
-gdb_test "print *(&b as *const \[i32; 0_0\])" " = \\\[\\\]"
+gdb_test "print b" [quotemeta {$@DECIMAL = []}]
+gdb_test "ptype b" [quotemeta {type = [i32; 0]}]
+gdb_test "print *(&b as *const \[i32; 0\])" [quotemeta {$@DECIMAL = []}]
+gdb_test "print *(&b as *const \[i32; 0_0\])" [quotemeta {$@DECIMAL = []}]
 
-gdb_test "print c" " = 99"
-gdb_test "ptype c" " = i32"
-gdb_test "print sizeof(c)" " = 4"
+gdb_test "print c" [quotemeta {$@DECIMAL = 99}]
+gdb_test "ptype c" "type = i32"
+gdb_test "print sizeof(c)" [quotemeta {$@DECIMAL = 4}]
 
-gdb_test "print c = 87" " = \\(\\)"
+gdb_test "print c = 87" [quotemeta {$@DECIMAL = ()}]
 gdb_test "print c" " = 87" "print after assignment"
 gdb_test "print c += 3" " = \\(\\)"
 gdb_test "print c" " = 90" "print after plus assignment"
@@ -72,7 +72,7 @@ gdb_test "whatis f" "type = &str"
 gdb_test "print *(&f as *mut &str)" " = \"hi bob\"" \
     "print via cast to &str"
 
-gdb_test "print g" " = \\(\\*mut \\\[u8; 6\\\]\\) $hex b\"hi bob\""
+gdb_test "print g" [quotemeta {$@DECIMAL = (*mut [u8; 6]) @HEX b"hi bob"}]
 gdb_test "ptype g" " = \\*mut \\\[u8; 6\\\]"
 
 gdb_test "print v" " = simple::Something::Three"
diff --git a/gdb/testsuite/gdb.testsuite/quotemeta.exp b/gdb/testsuite/gdb.testsuite/quotemeta.exp
new file mode 100644
index 00000000000..e6b6d102e3d
--- /dev/null
+++ b/gdb/testsuite/gdb.testsuite/quotemeta.exp
@@ -0,0 +1,46 @@
+# Copyright 2025 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 the quotemeta facility.
+
+proc check_quotemeta {name input expected} {
+    gdb_assert {[quotemeta $input] == $expected} $name
+}
+
+proc quotemeta_fail {name input} {
+    gdb_assert {[catch {quotemeta $input}]} $name
+}
+
+check_quotemeta hex "@HEX" "$hex"
+check_quotemeta brace-hex "@{HEX}" "$hex"
+
+check_quotemeta decimal "@DECIMAL" "$decimal"
+check_quotemeta multi-brace "@{{{DECIMAL}}}" "$decimal"
+
+check_quotemeta regexp "@/hello/" hello
+check_quotemeta regexp-2 "@/hello/@/.*/" "hello.*"
+check_quotemeta quoting ".*" "\\.\\*"
+
+check_quotemeta dots @... .*
+check_quotemeta at @@ @
+
+check_quotemeta slash-regexp "@{{/.*/.*/}}" ".*/.*"
+
+check_quotemeta spaces "hello world" "hello\\s+world"
+
+check_quotemeta shy-brace "@{{DECIMAL}}@{{DECIMAL}}" "$decimal$decimal"
+
+quotemeta_fail mismatch-brace "@{{{DECIMAL}}"
+quotemeta_fail no-such-subst @NOTHING
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 23fe3b3424a..c98e3e979db 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -161,6 +161,7 @@ load_lib cache.exp
 load_lib gdb-utils.exp
 load_lib memory.exp
 load_lib check-test-names.exp
+load_lib quotemeta.exp
 
 # The path to the GDB binary to test.
 global GDB
diff --git a/gdb/testsuite/lib/quotemeta.exp b/gdb/testsuite/lib/quotemeta.exp
new file mode 100644
index 00000000000..7752d755226
--- /dev/null
+++ b/gdb/testsuite/lib/quotemeta.exp
@@ -0,0 +1,137 @@
+# Copyright 2025 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/>.
+
+# Friendlier regexp facility.
+
+# Map from quotemeta names to regular expressions.  Internal to this
+# module.
+array set _quotemeta {}
+
+# This is a friendlier regexp facility.  It takes an input "quotemeta"
+# string and returns a regular expression.  The intent is to use
+# quotemeta expressions wherever a complicated regexp might otherwise
+# be needed.
+#
+# Any ordinary text in a quotemeta expression simply matches itself.
+# Such text is properly regexp-quoted in the return value.  So, the
+# quotemeta expression "xyz." matches exactly the text "xyz." -- the
+# "." here is just a "."  and not a special regexp character, and the
+# resulting regular expression will reflect this.
+#
+# Special matches are introduced by the "@" character.  There are
+# various forms of this:
+#
+# 1. "@NAME", where NAME was previously registered by
+#    define_quotemeta.  This simply expands to whatever regexp was
+#    provided at registration time.  For example, the predefined @HEX
+#    matches a hex constant starting with 0x.
+#
+# 2. "@...".  This expands to ".*" and is a just a handy catch-all.
+#
+# 3. "@@".  This matches a single "@".
+#
+# 4. "@/xyz/".  This matches the regular expression "xyz".  Note that
+#    in this case, "xyz" may not contain a "/".
+#
+# 5. "@{...}", "@{{...}}", etc.  This is "raw string" style, where any
+#    number of leading braces can be used, and then the substitution
+#    must end with the corresponding number of closing braces.  The
+#    contents are then recognized as in cases 1..4.  For example,
+#    @{NAME} and @NAME are the same.  The {} notation is handy when
+#    you want to match a "/" in a regexp, and {{..}} is handy if you
+#    want to match a "}" in a regexp.  @{...} is also useful when you
+#    want to make the boundary between a match and non-whitespace text
+#    more clear, like "@HEXname" versus "@{HEX}name".
+#
+# Finally, a space in a quotemeta string always matches any amount of
+# whitespace (at least one).  So, "@HEX hi" will match "0x23 hi" but
+# also "0x23   hi".  This is handy when dissecting gdb table output.
+proc quotemeta {str} {
+    set result ""
+
+    # Perform the quotemeta substitutions.  This is done in a somewhat
+    # odd way so that "@" can be supported inside a regular
+    # expression.  I.e., "@/...@.../" is valid.  This lets users avoid
+    # excess quoting.
+    while true {
+	regexp -- "^(\[^@\]*)(@(.*))?$" $str ignore prefix full_at at_text
+	append result [string_to_regexp $prefix]
+	if {$full_at == ""} {
+	    # No more substitutions, we're done.
+	    break
+	}
+
+	if {[regexp -- "^\{+" $at_text braces]} {
+	    set closer [string repeat "\}" [string length $braces]]
+	    # We want a non-greedy match here in case the input looks
+	    # like @{X}...stuff...@{Y}.
+	    if {![regexp -- "^${braces}(.*?)${closer}(.*)$" $at_text ignore \
+		      at_text remainder]} {
+		error "invalid quotemeta with @$braces..."
+	    }
+	} else {
+	    # Match the usual forms.
+	    if {![regexp -- "^(\\.\\.\\.|\[A-Z\]\[A-Z_\]*|@|/\[^/\]*/)(.*)$" \
+		      $at_text ignore at_text remainder]} {
+		error "invalid quotemeta expression"
+	    }
+	}
+
+	# Now AT_TEXT is the quotemeta substitution.
+	if {$at_text == "..."} {
+	    append result ".*"
+	} elseif {$at_text == "@"} {
+	    append result "@"
+	} elseif {[regexp -- "^/(.*)/$" $at_text ignore rx]} {
+	    # Note that here we don't use [^/] in the regexp, because
+	    # we want to support @{/blah/blah/}, where the regexp has
+	    # an embedded "/".
+	    append result $rx
+	} else {
+	    append result $::_quotemeta($at_text)
+	}
+
+	set str $remainder
+    }
+
+    # Now replace any whitespace with a regular expression that will
+    # match any amount of whitespace.  This is done after constructing
+    # the regular expression so we don't regexp-quote the \s+.
+    return [regsub -all -- "\\s+" $result "\\\\s+"]
+}
+
+# Define a new quotemeta substitution.
+#
+# NAME is the name that is used in the quotemeta string.  While the
+# implementation doesn't really care, for now we force this to be an
+# upper-case identifier, that is, starts with a capital letter and
+# only contains capital letters and underscores.
+#
+# RX is the regular expression that is used to implement the matching
+# for this name.
+proc define_quotemeta {name rx} {
+    if {![regexp -- "^\[A-Z\]\[A-Z_\]*$" $name]} {
+	error "bad quotemeta name"
+    }
+
+    set ::_quotemeta($name) $rx
+}
+
+# Some pre-defined quotemeta operators, available everywhere.
+
+# Match a hex number.
+define_quotemeta HEX $hex
+# Match a decimal number.
+define_quotemeta DECIMAL $decimal


More information about the Gdb-cvs mailing list