# Copyright 2019-2021 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 . # Make it easier to run the 'info modules' command (using # GDBInfoModules), and the 'info module ...' commands (using # GDBInfoModuleContents) and process the output. # # The difficulty we run into is that different versions of gFortran # include different helper modules which show up in the results. The # procedures in this library help process those parts of the output we # actually want to check, while ignoring those parts that we don't # care about. # # For each namespace GDBInfoModules and GDBInfoModuleContents, there's # a run_command proc, use this to run a command and capture the # output. Then make calls to check_header, check_entry, and # check_no_entry to ensure the output was as expected. namespace eval GDBInfoSymbols { # A string that is the header printed by GDB immediately after the # 'info [modules|types|functions|variables]' command has been issued. variable _header # A list of entries extracted from the output of the command. # Each entry is a filename, a line number, and the rest of the # text describing the entry. If an entry has no line number then # it is replaced with the text NONE. variable _entries # The string that is the complete last command run. variable _last_command # Add a new entry to the _entries list. proc _add_entry { filename lineno text } { variable _entries set entry [list $filename $lineno $text] lappend _entries $entry } # Run the 'info modules' command, passing ARGS as extra arguments # to the command. Process the output storing the results within # the variables in this namespace. # # The results of any previous call to run_command are discarded # when this is called. proc run_command { cmd { testname "" } } { global gdb_prompt variable _header variable _entries variable _last_command if {![regexp -- "^info (modules|types|variables|functions)" $cmd]} { perror "invalid command" } set _header "" set _entries [list] set _last_command $cmd if { $testname == "" } { set testname $cmd } send_gdb "$cmd\n" gdb_expect { -re "^$cmd\r\n" { # Match the original command echoed back to us. } timeout { fail "$testname (timeout)" return 0 } } gdb_expect { -re "^\r\n" { # Found the blank line after the header, we're done # parsing the header now. } -re "^\[ \t]*(\[^\r\n\]+)\r\n" { set str $expect_out(1,string) if { $_header == "" } { set _header $str } else { set _header "$_header $str" } exp_continue } timeout { fail "$testname (timeout)" return 0 } } set current_file "" gdb_expect { -re "^File (\[^\r\n\]+):\r\n" { set current_file $expect_out(1,string) exp_continue } -re "^(\[0-9\]+):\[ \t\]+(\[^\r\n\]+)\r\n" { set lineno $expect_out(1,string) set text $expect_out(2,string) if { $current_file == "" } { fail "$testname (missing filename)" return 0 } _add_entry $current_file $lineno $text exp_continue } -re "^\[ \t\]+(\[^\r\n\]+)\r\n" { set lineno "NONE" set text $expect_out(1,string) if { $current_file == "" } { fail "$testname (missing filename)" return 0 } _add_entry $current_file $lineno $text exp_continue } -re "^\r\n" { exp_continue } -re "^$gdb_prompt $" { # All done. } timeout { fail "$testname (timeout)" return 0 } } pass $testname return 1 } # Check that the header held in _header matches PATTERN. Use # TESTNAME as the name of the test, or create a suitable default # test name based on the last command. proc check_header { pattern { testname "" } } { variable _header variable _last_command if { $testname == "" } { set testname "$_last_command: check header" } gdb_assert {[regexp -- $pattern $_header]} $testname } # Check that we have an entry in _entries matching FILENAME, # LINENO, and TEXT. If LINENO is the empty string it is replaced # with the string NONE in order to match a similarly missing line # number in the output of the command. # # TESTNAME is the name of the test, or a default will be created # based on the last command run and the arguments passed here. # # If a matching entry is found then it is removed from the # _entries list, this allows us to check for duplicates using the # check_no_entry call. proc check_entry { filename lineno text { testname "" } } { variable _entries variable _last_command if { $testname == "" } { set testname \ "$_last_command: check for entry '$filename', '$lineno', '$text'" } if { $lineno == "" } { set lineno "NONE" } set new_entries [list] set found_match 0 foreach entry $_entries { if {!$found_match} { set f [lindex $entry 0] set l [lindex $entry 1] set t [lindex $entry 2] if { [regexp -- $filename $f] \ && [regexp -- $lineno $l] \ && [regexp -- $text $t] } { set found_match 1 } else { lappend new_entries $entry } } else { lappend new_entries $entry } } set _entries $new_entries gdb_assert { $found_match } $testname } # Check that there is no entry in the _entries list matching # FILENAME, LINENO, and TEXT. The LINENO and TEXT are optional, # and will be replaced with '.*' if missing. # # If LINENO is the empty string then it will be replaced with the # string NONE in order to match against missing line numbers in # the output of the command. # # TESTNAME is the name of the test, or a default will be built # from the last command run and the arguments passed here. # # This can be used after a call to check_entry to ensure that # there are no further matches for a particular file in the # output. proc check_no_entry { filename { lineno ".*" } { text ".*" } \ { testname "" } } { variable _entries variable _last_command if { $testname == "" } { set testname \ "$_last_command: check no matches for '$filename', '$lineno', and '$text'" } if { $lineno == "" } { set lineno "NONE" } foreach entry $_entries { set f [lindex $entry 0] set l [lindex $entry 1] set t [lindex $entry 2] if { [regexp -- $filename $f] \ && [regexp -- $lineno $l] \ && [regexp -- $text $t] } { fail $testname } } pass $testname } } namespace eval GDBInfoModuleSymbols { # A string that is the header printed by GDB immediately after the # 'info modules (variables|functions)' command has been issued. variable _header # A list of entries extracted from the output of the command. # Each entry is a filename, a module name, a line number, and the # rest of the text describing the entry. If an entry has no line # number then it is replaced with the text NONE. variable _entries # The string that is the complete last command run. variable _last_command # Add a new entry to the _entries list. proc _add_entry { filename module lineno text } { variable _entries set entry [list $filename $module $lineno $text] lappend _entries $entry } # Run the 'info module ....' command, passing ARGS as extra # arguments to the command. Process the output storing the # results within the variables in this namespace. # # The results of any previous call to run_command are discarded # when this is called. proc run_command { cmd { testname "" } } { global gdb_prompt variable _header variable _entries variable _last_command if {![regexp -- "^info module (variables|functions)" $cmd]} { perror "invalid command: '$cmd'" } set _header "" set _entries [list] set _last_command $cmd if { $testname == "" } { set testname $cmd } send_gdb "$cmd\n" gdb_expect { -re "^$cmd\r\n" { # Match the original command echoed back to us. } timeout { fail "$testname (timeout)" return 0 } } gdb_expect { -re "^\r\n" { # Found the blank line after the header, we're done # parsing the header now. } -re "^\[ \t\]*(\[^\r\n\]+)\r\n" { set str $expect_out(1,string) if { $_header == "" } { set _header $str } else { set _header "$_header $str" } exp_continue } timeout { fail "$testname (timeout)" return 0 } } set current_module "" set current_file "" gdb_expect { -re "^Module \"(\[^\"\]+)\":\r\n" { set current_module $expect_out(1,string) exp_continue } -re "^File (\[^\r\n\]+):\r\n" { if { $current_module == "" } { fail "$testname (missing module)" return 0 } set current_file $expect_out(1,string) exp_continue } -re "^(\[0-9\]+):\[ \t\]+(\[^\r\n\]+)\r\n" { set lineno $expect_out(1,string) set text $expect_out(2,string) if { $current_module == "" } { fail "$testname (missing module)" return 0 } if { $current_file == "" } { fail "$testname (missing filename)" return 0 } _add_entry $current_file $current_module \ $lineno $text exp_continue } -re "^\[ \t\]+(\[^\r\n\]+)\r\n" { set lineno "NONE" set text $expect_out(1,string) if { $current_module == "" } { fail "$testname (missing module)" return 0 } if { $current_file == "" } { fail "$testname (missing filename)" return 0 } _add_entry $current_file $current_module \ $lineno $text exp_continue } -re "^\r\n" { exp_continue } -re "^$gdb_prompt $" { # All done. } timeout { fail "$testname (timeout)" return 0 } } pass $testname return 1 } # Check that the header held in _header matches PATTERN. Use # TESTNAME as the name of the test, or create a suitable default # test name based on the last command. proc check_header { pattern { testname "" } } { variable _header variable _last_command if { $testname == "" } { set testname "$_last_command: check header" } gdb_assert {[regexp -- $pattern $_header]} $testname } # Check that we have an entry in _entries matching FILENAME, # MODULE, LINENO, and TEXT. If LINENO is the empty string it is # replaced with the string NONE in order to match a similarly # missing line number in the output of the command. # # TESTNAME is the name of the test, or a default will be created # based on the last command run and the arguments passed here. # # If a matching entry is found then it is removed from the # _entries list, this allows us to check for duplicates using the # check_no_entry call. # # If OPTIONAL, don't generate a FAIL for a mismatch, but use UNSUPPORTED # instead. proc check_entry_1 { filename module lineno text optional testname } { variable _entries variable _last_command if { $testname == "" } { set testname \ "$_last_command: check for entry '$filename', '$lineno', '$text'" } if { $lineno == "" } { set lineno "NONE" } set new_entries [list] set found_match 0 foreach entry $_entries { if {!$found_match} { set f [lindex $entry 0] set m [lindex $entry 1] set l [lindex $entry 2] set t [lindex $entry 3] if { [regexp -- $filename $f] \ && [regexp -- $module $m] \ && [regexp -- $lineno $l] \ && [regexp -- $text $t] } { set found_match 1 } else { lappend new_entries $entry } } else { lappend new_entries $entry } } set _entries $new_entries if { $optional && ! $found_match } { unsupported $testname } else { gdb_assert { $found_match } $testname } } # Call check_entry_1 with OPTIONAL == 0. proc check_entry { filename module lineno text { testname "" } } { check_entry_1 $filename $module $lineno $text 0 $testname } # Call check_entry_1 with OPTIONAL == 1. proc check_optional_entry { filename module lineno text { testname "" } } { check_entry_1 $filename $module $lineno $text 1 $testname } # Check that there is no entry in the _entries list matching # FILENAME, MODULE, LINENO, and TEXT. The LINENO and TEXT are # optional, and will be replaced with '.*' if missing. # # If LINENO is the empty string then it will be replaced with the # string NONE in order to match against missing line numbers in # the output of the command. # # TESTNAME is the name of the test, or a default will be built # from the last command run and the arguments passed here. # # This can be used after a call to check_entry to ensure that # there are no further matches for a particular file in the # output. proc check_no_entry { filename module { lineno ".*" } \ { text ".*" } { testname "" } } { variable _entries variable _last_command if { $testname == "" } { set testname \ "$_last_command: check no matches for '$filename', '$lineno', and '$text'" } if { $lineno == "" } { set lineno "NONE" } foreach entry $_entries { set f [lindex $entry 0] set m [lindex $entry 1] set l [lindex $entry 2] set t [lindex $entry 3] if { [regexp -- $filename $f] \ && [regexp -- $module $m] \ && [regexp -- $lineno $l] \ && [regexp -- $text $t] } { fail $testname } } pass $testname } }