44cbc79730adfd2bd568e2c7757c603c66a94cbd
[binutils-gdb.git] / gdb / testsuite / lib / tuiterm.exp
1 # Copyright 2019-2020 Free Software Foundation, Inc.
2
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 3 of the License, or
6 # (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16 # An ANSI terminal emulator for expect.
17
18 # The expect "spawn" function puts the tty name into the spawn_out
19 # array; but dejagnu doesn't export this globally. So, we have to
20 # wrap spawn with our own function, so that we can capture this value.
21 # The value is later used in calls to stty.
22 proc tuiterm_spawn { args } {
23 set result [uplevel builtin_spawn $args]
24 global gdb_spawn_name
25 upvar spawn_out spawn_out
26 if { [info exists spawn_out] } {
27 set gdb_spawn_name $spawn_out(slave,name)
28 } else {
29 unset gdb_spawn_name
30 }
31 return $result
32 }
33
34 # Initialize tuiterm.exp environment.
35 proc tuiterm_env_init { } {
36 # Override spawn with tui_spawn.
37 rename spawn builtin_spawn
38 rename tuiterm_spawn spawn
39 }
40
41 # Finalize tuiterm.exp environment.
42 proc tuiterm_env_finish { } {
43 # Restore spawn.
44 rename spawn tuiterm_spawn
45 rename builtin_spawn spawn
46 }
47
48 namespace eval Term {
49 variable _rows
50 variable _cols
51 variable _chars
52
53 variable _cur_x
54 variable _cur_y
55
56 variable _attrs
57
58 variable _last_char
59
60 variable _resize_count
61
62 # If ARG is empty, return DEF: otherwise ARG. This is useful for
63 # defaulting arguments in CSIs.
64 proc _default {arg def} {
65 if {$arg == ""} {
66 return $def
67 }
68 return $arg
69 }
70
71 # Erase in the line Y from SX to just before EX.
72 proc _clear_in_line {sx ex y} {
73 variable _attrs
74 variable _chars
75 set lattr [array get _attrs]
76 while {$sx < $ex} {
77 set _chars($sx,$y) [list " " $lattr]
78 incr sx
79 }
80 }
81
82 # Erase the lines from SY to just before EY.
83 proc _clear_lines {sy ey} {
84 variable _cols
85 while {$sy < $ey} {
86 _clear_in_line 0 $_cols $sy
87 incr sy
88 }
89 }
90
91 # Beep.
92 proc _ctl_0x07 {} {
93 }
94
95 # Backspace.
96 proc _ctl_0x08 {} {
97 variable _cur_x
98 incr _cur_x -1
99 if {$_cur_x < 0} {
100 variable _cur_y
101 variable _cols
102 set _cur_x [expr {$_cols - 1}]
103 incr _cur_y -1
104 if {$_cur_y < 0} {
105 set _cur_y 0
106 }
107 }
108 }
109
110 # Linefeed.
111 proc _ctl_0x0a {} {
112 variable _cur_y
113 variable _rows
114 incr _cur_y 1
115 if {$_cur_y >= $_rows} {
116 error "FIXME scroll"
117 }
118 }
119
120 # Carriage return.
121 proc _ctl_0x0d {} {
122 variable _cur_x
123 set _cur_x 0
124 }
125
126 # Make room for characters.
127 proc _csi_@ {args} {
128 set n [_default [lindex $args 0] 1]
129 variable _cur_x
130 variable _cur_y
131 variable _chars
132 set in_x $_cur_x
133 set out_x [expr {$_cur_x + $n}]
134 for {set i 0} {$i < $n} {incr i} {
135 set _chars($out_x,$_cur_y) $_chars($in_x,$_cur_y)
136 incr in_x
137 incr out_x
138 }
139 }
140
141 # Cursor Up.
142 proc _csi_A {args} {
143 variable _cur_y
144 set arg [_default [lindex $args 0] 1]
145 set _cur_y [expr {max ($_cur_y - $arg, 0)}]
146 }
147
148 # Cursor Down.
149 proc _csi_B {args} {
150 variable _cur_y
151 variable _rows
152 set arg [_default [lindex $args 0] 1]
153 set _cur_y [expr {min ($_cur_y + $arg, $_rows)}]
154 }
155
156 # Cursor Forward.
157 proc _csi_C {args} {
158 variable _cur_x
159 variable _cols
160 set arg [_default [lindex $args 0] 1]
161 set _cur_x [expr {min ($_cur_x + $arg, $_cols)}]
162 }
163
164 # Cursor Back.
165 proc _csi_D {args} {
166 variable _cur_x
167 set arg [_default [lindex $args 0] 1]
168 set _cur_x [expr {max ($_cur_x - $arg, 0)}]
169 }
170
171 # Cursor Next Line.
172 proc _csi_E {args} {
173 variable _cur_x
174 variable _cur_y
175 variable _rows
176 set arg [_default [lindex $args 0] 1]
177 set _cur_x 0
178 set _cur_y [expr {min ($_cur_y + $arg, $_rows)}]
179 }
180
181 # Cursor Previous Line.
182 proc _csi_F {args} {
183 variable _cur_x
184 variable _cur_y
185 variable _rows
186 set arg [_default [lindex $args 0] 1]
187 set _cur_x 0
188 set _cur_y [expr {max ($_cur_y - $arg, 0)}]
189 }
190
191 # Cursor Horizontal Absolute.
192 proc _csi_G {args} {
193 variable _cur_x
194 variable _cols
195 set arg [_default [lindex $args 0] 1]
196 set _cur_x [expr {min ($arg - 1, $_cols)}]
197 }
198
199 # Move cursor (don't know the official name of this one).
200 proc _csi_H {args} {
201 variable _cur_x
202 variable _cur_y
203 set _cur_y [expr {[_default [lindex $args 0] 1] - 1}]
204 set _cur_x [expr {[_default [lindex $args 1] 1] - 1}]
205 }
206
207 # Cursor Forward Tabulation.
208 proc _csi_I {args} {
209 set n [_default [lindex $args 0] 1]
210 variable _cur_x
211 variable _cols
212 incr _cur_x [expr {$n * 8 - $_cur_x % 8}]
213 if {$_cur_x >= $_cols} {
214 set _cur_x [expr {$_cols - 1}]
215 }
216 }
217
218 # Erase.
219 proc _csi_J {args} {
220 variable _cur_x
221 variable _cur_y
222 variable _rows
223 variable _cols
224 set arg [_default [lindex $args 0] 0]
225 if {$arg == 0} {
226 _clear_in_line $_cur_x $_cols $_cur_y
227 _clear_lines [expr {$_cur_y + 1}] $_rows
228 } elseif {$arg == 1} {
229 _clear_lines 0 [expr {$_cur_y - 1}]
230 _clear_in_line 0 $_cur_x $_cur_y
231 } elseif {$arg == 2} {
232 _clear_lines 0 $_rows
233 }
234 }
235
236 # Erase Line.
237 proc _csi_K {args} {
238 variable _cur_x
239 variable _cur_y
240 variable _cols
241 set arg [_default [lindex $args 0] 0]
242 if {$arg == 0} {
243 # From cursor to end.
244 _clear_in_line $_cur_x $_cols $_cur_y
245 } elseif {$arg == 1} {
246 _clear_in_line 0 $_cur_x $_cur_y
247 } elseif {$arg == 2} {
248 _clear_in_line 0 $_cols $_cur_y
249 }
250 }
251
252 # Delete lines.
253 proc _csi_M {args} {
254 variable _cur_y
255 variable _rows
256 variable _cols
257 variable _chars
258 set count [_default [lindex $args 0] 1]
259 set y $_cur_y
260 set next_y [expr {$y + 1}]
261 while {$count > 0 && $next_y < $_rows} {
262 for {set x 0} {$x < $_cols} {incr x} {
263 set _chars($x,$y) $_chars($x,$next_y)
264 }
265 incr y
266 incr next_y
267 incr count -1
268 }
269 _clear_lines $next_y $_rows
270 }
271
272 # Erase chars.
273 proc _csi_X {args} {
274 set n [_default [lindex $args 0] 1]
275 # Erase characters but don't move cursor.
276 variable _cur_x
277 variable _cur_y
278 variable _attrs
279 variable _chars
280 set lattr [array get _attrs]
281 set x $_cur_x
282 for {set i 0} {$i < $n} {incr i} {
283 set _chars($x,$_cur_y) [list " " $lattr]
284 incr x
285 }
286 }
287
288 # Backward tab stops.
289 proc _csi_Z {args} {
290 set n [_default [lindex $args 0] 1]
291 variable _cur_x
292 set _cur_x [expr {max (int (($_cur_x - 1) / 8) * 8 - ($n - 1) * 8, 0)}]
293 }
294
295 # Repeat.
296 proc _csi_b {args} {
297 variable _last_char
298 set n [_default [lindex $args 0] 1]
299 _insert [string repeat $_last_char $n]
300 }
301
302 # Line Position Absolute.
303 proc _csi_d {args} {
304 variable _cur_y
305 set _cur_y [expr {[_default [lindex $args 0] 1] - 1}]
306 }
307
308 # Select Graphic Rendition.
309 proc _csi_m {args} {
310 variable _attrs
311 foreach item $args {
312 switch -exact -- $item {
313 "" - 0 {
314 set _attrs(intensity) normal
315 set _attrs(fg) default
316 set _attrs(bg) default
317 set _attrs(underline) 0
318 set _attrs(reverse) 0
319 }
320 1 {
321 set _attrs(intensity) bold
322 }
323 2 {
324 set _attrs(intensity) dim
325 }
326 4 {
327 set _attrs(underline) 1
328 }
329 7 {
330 set _attrs(reverse) 1
331 }
332 22 {
333 set _attrs(intensity) normal
334 }
335 24 {
336 set _attrs(underline) 0
337 }
338 27 {
339 set _attrs(reverse) 1
340 }
341 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 {
342 set _attrs(fg) $item
343 }
344 39 {
345 set _attrs(fg) default
346 }
347 40 - 41 - 42 - 43 - 44 - 45 - 46 - 47 {
348 set _attrs(bg) $item
349 }
350 49 {
351 set _attrs(bg) default
352 }
353 }
354 }
355 }
356
357 # Insert string at the cursor location.
358 proc _insert {str} {
359 verbose "INSERT <<$str>>"
360 variable _cur_x
361 variable _cur_y
362 variable _rows
363 variable _cols
364 variable _attrs
365 variable _chars
366 set lattr [array get _attrs]
367 foreach char [split $str {}] {
368 set _chars($_cur_x,$_cur_y) [list $char $lattr]
369 incr _cur_x
370 if {$_cur_x >= $_cols} {
371 set _cur_x 0
372 incr _cur_y
373 if {$_cur_y >= $_rows} {
374 error "FIXME scroll"
375 }
376 }
377 }
378 }
379
380 # Initialize.
381 proc _setup {rows cols} {
382 global stty_init
383 set stty_init "rows $rows columns $cols"
384
385 variable _rows
386 variable _cols
387 variable _cur_x
388 variable _cur_y
389 variable _attrs
390 variable _resize_count
391
392 set _rows $rows
393 set _cols $cols
394 set _cur_x 0
395 set _cur_y 0
396 set _resize_count 0
397 array set _attrs {
398 intensity normal
399 fg default
400 bg default
401 underline 0
402 reverse 0
403 }
404
405 _clear_lines 0 $_rows
406 }
407
408 # Accept some output from gdb and update the screen. WAIT_FOR is
409 # a regexp matching the line to wait for. Return 0 on timeout, 1
410 # on success.
411 proc wait_for {wait_for} {
412 global expect_out
413 global gdb_prompt
414 variable _cur_x
415 variable _cur_y
416
417 set prompt_wait_for "$gdb_prompt \$"
418
419 while 1 {
420 gdb_expect {
421 -re "^\[\x07\x08\x0a\x0d\]" {
422 scan $expect_out(0,string) %c val
423 set hexval [format "%02x" $val]
424 verbose "+++ _ctl_0x${hexval}"
425 _ctl_0x${hexval}
426 }
427 -re "^\x1b(\[0-9a-zA-Z\])" {
428 verbose "+++ unsupported escape"
429 error "unsupported escape"
430 }
431 -re "^\x1b\\\[(\[0-9;\]*)(\[a-zA-Z@\])" {
432 set cmd $expect_out(2,string)
433 set params [split $expect_out(1,string) ";"]
434 verbose "+++ _csi_$cmd <<<$expect_out(1,string)>>>"
435 eval _csi_$cmd $params
436 }
437 -re "^\[^\x07\x08\x0a\x0d\x1b\]+" {
438 _insert $expect_out(0,string)
439 variable _last_char
440 set _last_char [string index $expect_out(0,string) end]
441 }
442
443 timeout {
444 # Assume a timeout means we somehow missed the
445 # expected result, and carry on.
446 return 0
447 }
448 }
449
450 # If the cursor appears just after the prompt, return. It
451 # isn't reliable to check this only after an insertion,
452 # because curses may make "unusual" redrawing decisions.
453 if {$wait_for == "$prompt_wait_for"} {
454 set prev [get_line $_cur_y $_cur_x]
455 } else {
456 set prev [get_line $_cur_y]
457 }
458 if {[regexp -- $wait_for $prev]} {
459 if {$wait_for == "$prompt_wait_for"} {
460 break
461 }
462 set wait_for $prompt_wait_for
463 }
464 }
465
466 return 1
467 }
468
469 # Like ::clean_restart, but ensures that gdb starts in an
470 # environment where the TUI can work. ROWS and COLS are the size
471 # of the terminal. EXECUTABLE, if given, is passed to
472 # clean_restart.
473 proc clean_restart {rows cols {executable {}}} {
474 global env stty_init
475 save_vars {env(TERM) stty_init} {
476 setenv TERM ansi
477 _setup $rows $cols
478 if {$executable == ""} {
479 ::clean_restart
480 } else {
481 ::clean_restart $executable
482 }
483 }
484 }
485
486 # Setup ready for starting the tui, but don't actually start it.
487 # Returns 1 on success, 0 if TUI tests should be skipped.
488 proc prepare_for_tui {} {
489 if {[skip_tui_tests]} {
490 return 0
491 }
492
493 gdb_test_no_output "set tui border-kind ascii"
494 gdb_test_no_output "maint set tui-resize-message on"
495 return 1
496 }
497
498 # Start the TUI. Returns 1 on success, 0 if TUI tests should be
499 # skipped.
500 proc enter_tui {} {
501 if {![prepare_for_tui]} {
502 return 0
503 }
504
505 command_no_prompt_prefix "tui enable"
506 return 1
507 }
508
509 # Send the command CMD to gdb, then wait for a gdb prompt to be
510 # seen in the TUI. CMD should not end with a newline -- that will
511 # be supplied by this function.
512 proc command {cmd} {
513 global gdb_prompt
514 send_gdb "$cmd\n"
515 set str [string_to_regexp $cmd]
516 set str "^$gdb_prompt $str"
517 wait_for $str
518 }
519
520 # As proc command, but don't wait for a initial prompt. This is used for
521 # inital terminal commands, where there's no prompt yet.
522 proc command_no_prompt_prefix {cmd} {
523 send_gdb "$cmd\n"
524 set str [string_to_regexp $cmd]
525 wait_for "^$str"
526 }
527
528 # Return the text of screen line N, without attributes. Lines are
529 # 0-based. If C is given, stop before column C. Columns are also
530 # zero-based.
531 proc get_line {n {c ""}} {
532 variable _rows
533 # This can happen during resizing, if the cursor seems to
534 # temporarily be off-screen.
535 if {$n >= $_rows} {
536 return ""
537 }
538
539 set result ""
540 variable _cols
541 variable _chars
542 set c [_default $c $_cols]
543 set x 0
544 while {$x < $c} {
545 append result [lindex $_chars($x,$n) 0]
546 incr x
547 }
548 return $result
549 }
550
551 # Get just the character at (X, Y).
552 proc get_char {x y} {
553 variable _chars
554 return [lindex $_chars($x,$y) 0]
555 }
556
557 # Get the entire screen as a string.
558 proc get_all_lines {} {
559 variable _rows
560 variable _cols
561 variable _chars
562
563 set result ""
564 for {set y 0} {$y < $_rows} {incr y} {
565 for {set x 0} {$x < $_cols} {incr x} {
566 append result [lindex $_chars($x,$y) 0]
567 }
568 append result "\n"
569 }
570
571 return $result
572 }
573
574 # Get the text just before the cursor.
575 proc get_current_line {} {
576 variable _cur_x
577 variable _cur_y
578 return [get_line $_cur_y $_cur_x]
579 }
580
581 # Helper function for check_box. Returns empty string if the box
582 # is found, description of why not otherwise.
583 proc _check_box {x y width height} {
584 set x2 [expr {$x + $width - 1}]
585 set y2 [expr {$y + $height - 1}]
586
587 if {[get_char $x $y] != "+"} {
588 return "ul corner"
589 }
590 if {[get_char $x $y2] != "+"} {
591 return "ll corner"
592 }
593 if {[get_char $x2 $y] != "+"} {
594 return "ur corner"
595 }
596 if {[get_char $x2 $y2] != "+"} {
597 return "lr corner"
598 }
599
600 # Note we do not check the full horizonal borders of the box.
601 # The top will contain a title, and the bottom may as well, if
602 # it is overlapped by some other border. However, at most a
603 # title should appear as '+-VERY LONG TITLE-+', so we can
604 # check for the '+-' on the left, and '-+' on the right.
605 if {[get_char [expr {$x + 1}] $y] != "-"} {
606 return "ul title padding"
607 }
608
609 if {[get_char [expr {$x2 - 1}] $y] != "-"} {
610 return "ul title padding"
611 }
612
613 # Now check the vertical borders.
614 for {set i [expr {$y + 1}]} {$i < $y2 - 1} {incr i} {
615 if {[get_char $x $i] != "|"} {
616 return "left side $i"
617 }
618 if {[get_char $x2 $i] != "|"} {
619 return "right side $i"
620 }
621 }
622
623 return ""
624 }
625
626 # Check for a box at the given coordinates.
627 proc check_box {test_name x y width height} {
628 set why [_check_box $x $y $width $height]
629 if {$why == ""} {
630 pass $test_name
631 } else {
632 dump_screen
633 fail "$test_name ($why)"
634 }
635 }
636
637 # Check whether the text contents of the terminal match the
638 # regular expression. Note that text styling is not considered.
639 proc check_contents {test_name regexp} {
640 set contents [get_all_lines]
641 if {![gdb_assert {[regexp -- $regexp $contents]} $test_name]} {
642 dump_screen
643 }
644 }
645
646 # Check the contents of a box on the screen. This is a little
647 # like check_contents, but doens't check the whole screen
648 # contents, only the contents of a single box. This procedure
649 # includes (effectively) a call to check_box to ensure there is a
650 # box where expected, if there is then the contents of the box are
651 # matched against REGEXP.
652 proc check_box_contents {test_name x y width height regexp} {
653 variable _chars
654
655 set why [_check_box $x $y $width $height]
656 if {$why != ""} {
657 dump_screen
658 fail "$test_name (box check: $why)"
659 return
660 }
661
662 # Now grab the contents of the box, join each line together
663 # with a newline character and match against REGEXP.
664 set result ""
665 for {set yy [expr {$y + 1}]} {$yy < [expr {$y + $height - 1}]} {incr yy} {
666 for {set xx [expr {$x + 1}]} {$xx < [expr {$x + $width - 1}]} {incr xx} {
667 append result [lindex $_chars($xx,$yy) 0]
668 }
669 append result "\n"
670 }
671
672 if {![gdb_assert {[regexp -- $regexp $result]} $test_name]} {
673 dump_screen
674 }
675 }
676
677 # A debugging function to dump the current screen, with line
678 # numbers.
679 proc dump_screen {} {
680 variable _rows
681 variable _cols
682 verbose -log "Screen Dump ($_cols x $_rows):"
683 for {set y 0} {$y < $_rows} {incr y} {
684 set fmt [format %5d $y]
685 verbose -log "$fmt [get_line $y]"
686 }
687 }
688
689 # Resize the terminal.
690 proc _do_resize {rows cols} {
691 variable _chars
692 variable _rows
693 variable _cols
694
695 set old_rows [expr {min ($_rows, $rows)}]
696 set old_cols [expr {min ($_cols, $cols)}]
697
698 # Copy locally.
699 array set local_chars [array get _chars]
700 unset _chars
701
702 set _rows $rows
703 set _cols $cols
704 _clear_lines 0 $_rows
705
706 for {set x 0} {$x < $old_cols} {incr x} {
707 for {set y 0} {$y < $old_rows} {incr y} {
708 set _chars($x,$y) $local_chars($x,$y)
709 }
710 }
711 }
712
713 proc resize {rows cols} {
714 variable _rows
715 variable _cols
716 variable _resize_count
717
718 global gdb_spawn_name
719 # expect handles each argument to stty separately. This means
720 # that gdb will see SIGWINCH twice. Rather than rely on this
721 # behavior (which, after all, could be changed), we make it
722 # explicit here. This also simplifies waiting for the redraw.
723 _do_resize $rows $_cols
724 stty rows $_rows < $gdb_spawn_name
725 # Due to the strange column resizing behavior, and because we
726 # don't care about this intermediate resize, we don't check
727 # the size here.
728 wait_for "@@ resize done $_resize_count"
729 incr _resize_count
730 # Somehow the number of columns transmitted to gdb is one less
731 # than what we request from expect. We hide this weird
732 # details from the caller.
733 _do_resize $_rows $cols
734 stty columns [expr {$_cols + 1}] < $gdb_spawn_name
735 wait_for "@@ resize done $_resize_count, size = ${_cols}x${rows}"
736 incr _resize_count
737 }
738 }