[RFC] Improve testsuite for poor expect behavior

Pierre Muller
2009-06-12T23:00:44Z

I am trying to run the testsuite in a cygwin environment,
but for mingw32 or djgpp targets, using native builds of GDB.

For mingw32, the main problem is that
there are extra ^M generated in the output.

For a while I was wondering if I could change all
"\r\n" into "\r+\n" in the different test .exp files.

But this would be a huge number of changes,
and replacing all in wrong especially inside things like "\[^\r\n\]*"
which means anything but a carriage return or line feed.
Adding a "+" here would probably give lots of new errors.

I finally came up with a smaller solutions,
that seems to work pretty well:
Simply replace all occurrences of "\r\n" by "\r+\n"
in the expcode argument of gdb_expect function.
This would give the same problem as above with "\r\n" inside square
brackets.
The solution is to first replace any "\r\n" inside square brackets by
"\rzz\n"
to protect them again adding the "+",
and transform the "\rzz\n" inside square brackets back into "\r\n" after.

As long as there are no "\r\n" inside variables to be substituted later,
this works fine.
The main remaining problem is gdb_expect_list,
because it does use such variables with enclosed newlines.

There is a second aspect, which is mainly a problem of
the cygwin expect: GDB run inside expect does not believe that
they are connected to a terminal, which means that queries are
answered by their default values.
A large part of the patch below is devoted to adding pattern
that recognize correctly the cases where a query is answered automatically.

I checked this patch on gcc-farm the
testsuite results are totally unaffected.

Is this patch something that could be acceptable,
or does it go too far in trying to compensate for expect
limitations?

Pierre Muller
Pascal language support maintainer for GDB

PS: For DJGPP, I had to add some special tricks to
be able to run expect on it, I will try to send this
in a separate email.

2009-06-12 Pierre Muller <muller@ics.u-strasbg.fr>

* lib/gdb.exp (fuzzy_newline): New global.
(input_not_from_terminal): New global.
(gdb_input_not_from_terminal): New procedure.
(gdb_proc_unload): Add pattern corresponding to automated answer
if not from terminal.
(delete_breakpoints): Likewise.
(gdb_run_mcd): Likewise.
(gdb_start_cmd): Likewise.
(gdb_internal_error_resync): Likewise.
(gdb_reinitialize_dir): Likewise.
(default_gdb_exit): Likewise.
(gdb_file_cmd): Likewise.
(rerun_to_main): Likewise.
(gdb_compile): Also compile set_unbuffered_output
for djgpp target.
(gdb_expect): If fuzzy_newline is set, replace all "\r\n"
by "\r+\n" in expcode.

Index: src/gdb/testsuite/lib/gdb.exp
===================================================================
RCS file: /cvs/src/src/gdb/testsuite/lib/gdb.exp,v
retrieving revision 1.114
diff -u -p -r1.114 src/gdb/testsuite/lib/gdb.exp
--- src/gdb/testsuite/lib/gdb.exp 22 Apr 2009 19:46:19 -0000
1.114
+++ src/gdb/testsuite/lib/gdb.exp 12 Jun 2009 15:14:16 -0000
@@ -93,6 +93,30 @@ if ![info exists env(EXEEXT)] {
set EXEEXT $env(EXEEXT)
}

+global fuzzy_newline
+if { [istarget "*-*-mingw*"] || [istarget "*-*-*djgpp"] } {
+ set fuzzy_newline 1;
+}
+
+global input_not_from_terminal
+set input_not_from_terminal 0;
+#if { [istarget "*-*-mingw*"] || [istarget "*-*-*djgpp"] \
+# || [istarget "*-*-cygwin*"] } {
+# set input_not_from_terminal 1;
+#}
+
+proc gdb_input_not_from_terminal { context } {
+ global input_not_from_terminal
+ if {$input_not_from_terminal} {
+ verbose "Input not from terminal in $context"
+ } else {
+ fail "GDB does not seem to believe it is on a terminal\n"
+ verbose "Setting input_not_from_terminal to 1"
+ set input_not_from_terminal 1;
+ }
+}
+
+
set octal "\[0-7\]+"

### Only procedures should come after this point.
@@ -137,11 +161,23 @@ proc gdb_unload {} {
gdb_expect 60 {
-re "No executable file now\[^\r\n\]*\[\r\n\]" { exp_continue }
-re "No symbol file now\[^\r\n\]*\[\r\n\]" { exp_continue }
+ -re "A program is being debugged already..*Kill it.*y or n.*answered
Y; input not from terminal."\
+ { verbose "\t\tKilling previous program being debugged (not from
terminal)"
+ gdb_input_not_from_terminal "gdb_unload"
+ exp_continue
+ }
+
-re "A program is being debugged already..*Kill it.*y or n. $"\
{ send_gdb "y\n"
verbose "\t\tKilling previous program being debugged"
exp_continue
}
+ -re "Discard symbol table from .*y or n.*answered Y; input not from
terminal." {
+ verbose "Discard symbol table (not from terminal)"
+ gdb_input_not_from_terminal "gdb_unload symbols"
+ exp_continue
+ }
+
-re "Discard symbol table from .*y or n.*$" {
send_gdb "y\n"
exp_continue
@@ -168,7 +204,12 @@ proc delete_breakpoints {} {
#
send_gdb "delete breakpoints\n"
gdb_expect 100 {
- -re "Delete all breakpoints.*y or n.*$" {
+ -re "Delete all breakpoints.*y or n.*answered Y; input not from
terminal." {
+ gdb_input_not_from_terminal "delete_breakpoints";
+ exp_continue
+ }
+
+ -re "Delete all breakpoints.*y or n. $" {
send_gdb "y\n";
exp_continue
}
@@ -217,7 +258,7 @@ proc gdb_run_cmd {args} {
}
send_gdb "continue\n";
gdb_expect 60 {
- -re "Continu\[^\r\n\]*\[\r\n\]" {}
+ -re "Continu\[^\r\n\]*\[\r\n\]+" {}
default {}
}
return;
@@ -240,7 +281,7 @@ proc gdb_run_cmd {args} {
}
set start_attempt [expr $start_attempt + 1];
gdb_expect 30 {
- -re "Continuing at \[^\r\n\]*\[\r\n\]" {
+ -re "Continuing at \[^\r\n\]*\[\r\n\]+" {
set start_attempt 0;
}
-re "No symbol \"_start\" in current.*$gdb_prompt $" {
@@ -253,6 +294,10 @@ proc gdb_run_cmd {args} {
-re "No symbol.*context.*$gdb_prompt $" {
set start_attempt 0;
}
+ -re "Line.* Jump anyway.*y or n.*answered Y; input not from
terminal." {
+ gdb_input_not_from_terminal "gdb_run_cmd jump"
+ }
+
-re "Line.* Jump anyway.*y or n. $" {
send_gdb "y\n"
}
@@ -288,6 +333,11 @@ proc gdb_run_cmd {args} {
# Use -notransfer here so that test cases (like chng-sym.exp)
# may test for additional start-up messages.
gdb_expect 60 {
+ -re "The program .* has been started already.*y or n.*answered Y;
input not from terminal." {
+ gdb_input_not_from_terminal "gdb_run_cmd run"
+ exp_continue
+ }
+
-re "The program .* has been started already.*y or n. $" {
send_gdb "y\n"
exp_continue
@@ -319,6 +369,11 @@ proc gdb_start_cmd {args} {

send_gdb "start $args\n"
gdb_expect 60 {
+ -re "The program .* has been started already.*y or n.*answered Y;
input not from terminal." {
+ gdb_input_not_from_terminal "gdb_start_cmd start"
+ exp_continue
+ }
+
-re "The program .* has been started already.*y or n. $" {
send_gdb "y\n"
exp_continue
@@ -506,10 +561,20 @@ proc gdb_internal_error_resync {} {
set count 0
while {$count < 10} {
gdb_expect {
+ -re "Quit this debugging session\\? \\(y or n\\).*answered N;
input not from terminal." {
+ gdb_input_not_from_terminal "gdb_internal_error_resync Quit"
+ incr count
+ }
+
-re "Quit this debugging session\\? \\(y or n\\) $" {
send_gdb "n\n"
incr count
}
+ -re "Create a core file of GDB\\? \\(y or n\\).*answered Y;
input not from terminal." {
+ gdb_input_not_from_terminal "gdb_internal_error_resync
Create core"
+ incr count
+ }
+
-re "Create a core file of GDB\\? \\(y or n\\) $" {
send_gdb "n\n"
incr count
@@ -988,19 +1053,20 @@ proc gdb_reinitialize_dir { subdir } {
}
send_gdb "dir\n"
gdb_expect 60 {
+ -re "Reinitialize source path to empty.*y or n. .answered Y; input
not from terminal." {
+ gdb_input_not_from_terminal "gdb_reinitialize_dir Reinitialize"
+ exp_continue
+ }
+
-re "Reinitialize source path to empty.*y or n. " {
send_gdb "y\n"
+ exp_continue
+ }
+ -re "Source directories searched.*$gdb_prompt $" {
+ send_gdb "dir $subdir\n"
gdb_expect 60 {
-re "Source directories searched.*$gdb_prompt $" {
- send_gdb "dir $subdir\n"
- gdb_expect 60 {
- -re "Source directories searched.*$gdb_prompt $" {
- verbose "Dir set to $subdir"
- }
- -re "$gdb_prompt $" {
- perror "Dir \"$subdir\" failed."
- }
- }
+ verbose "Dir set to $subdir"
}
-re "$gdb_prompt $" {
perror "Dir \"$subdir\" failed."
@@ -1033,6 +1099,10 @@ proc default_gdb_exit {} {
if { [is_remote host] && [board_info host exists fileid] } {
send_gdb "quit\n";
gdb_expect 10 {
+ -re "y or n.*answered Y; input not from terminal." {
+ gdb_input_not_from_terminal "gdb_reinitialize_dir
Reinitialize"
+ exp_continue;
+ }
-re "y or n" {
send_gdb "y\n";
exp_continue;
@@ -1090,6 +1160,11 @@ proc gdb_file_cmd { arg } {
# of the testsuite, preserve this behavior.
send_gdb "kill\n"
gdb_expect 120 {
+ -re "Kill the program being debugged. .y or n.*answered Y; input not
from terminal." {
+ verbose "\t\tKilling previous program being debugged"
+ gdb_input_not_from_terminal "gdb_file_cmd kill"
+ exp_continue
+ }
-re "Kill the program being debugged. .y or n. $" {
send_gdb "y\n"
verbose "\t\tKilling previous program being debugged"
@@ -1112,6 +1187,21 @@ proc gdb_file_cmd { arg } {
set gdb_file_cmd_debug_info "debug"
return 0
}
+ -re "Load new symbol table from \".*\".*y or n.*answered Y; input
not from terminal." {
+ gdb_input_not_from_terminal "gdb_file_cmd file"
+ gdb_expect 120 {
+ -re "Reading symbols from.*done.*$gdb_prompt $" {
+ verbose "\t\tLoaded $arg with new symbol table into
$GDB"
+ set gdb_file_cmd_debug_info "debug"
+ return 0
+ }
+ timeout {
+ perror "(timeout) Couldn't load $arg, other program
already loaded."
+ return -1
+ }
+ }
+ }
+
-re "Load new symbol table from \".*\".*y or n. $" {
send_gdb "y\n"
gdb_expect 120 {
@@ -1743,6 +1833,7 @@ proc gdb_compile {source dest type optio

if { $type == "executable" } {
if { ([istarget "*-*-mingw*"]
+ || [istarget "*-*-*djgpp"]
|| [istarget "*-*-cygwin*"])} {
# Force output to unbuffered mode, by linking in an object file
# with a global contructor that calls setvbuf.
@@ -1986,6 +2077,24 @@ proc gdb_expect { args } {
}
}

+ global fuzzy_newline;
+
+ if { [info exists fuzzy_newline] && $fuzzy_newline } {
+ verbose "Special replacement in gdb_expect entry
expcode=\"$expcode\""
+ set subst1 [regsub -all {\\\[([^]]*)\\r\\n([^]]*)\\\]} "$expcode" \
+ {\\[\1\\rzz\\n\2\\]} expcode1]
+ set subst2 [regsub -all {\\r+\\n} $expcode1 {\\r\\n} expcode2]
+ set subst3 [regsub -all {\\r\\n} $expcode2 {\\r+\\n} expcode3]
+ set subst4 [regsub -all {\\\[([^]]*)\\rzz\\n([^]]*)\\\]} $expcode3 \
+ {\\[\1\\r\\n\2\\]} expcode4]
+ set subst [expr $subst1 + $subst2 + $subst3 + $subst4];
+ verbose "Special replacement in gdb_expect returned
expcode=\"$expcode4\""
+ if { $subst1 != 0 || $subst2 != 0 || $subst3 != 0 } {
+ verbose "Special replacement gdb_expect substs=$subst"
+ verbose "subst1=$subst1 subst2=$subst2 subst3=$subst3
subst4=$subst4" 3
+ set expcode $expcode4;
+ }
+ }
global suppress_flag;
global remote_suppress_flag;
if [info exists remote_suppress_flag] {
@@ -2573,6 +2683,10 @@ proc rerun_to_main {} {
} else {
send_gdb "run\n"
gdb_expect {
+ -re "The program .* has been started already.*y or n.*answered Y;
input not from terminal." {
+ gdb_input_not_from_terminal "rerun_to_main"
+ exp_continue
+ }
-re "The program .* has been started already.*y or n. $" {
send_gdb "y\n"
exp_continue