# selftest_dialog.exp — drives cdialog over a REAL pty (BRINTOS-288). # # dialog is a curses program whose ENTIRE PURPOSE is to be driven from a shell # script, and that shape is what makes it worth testing here: unlike nano, every # widget hands back a MACHINE-READABLE ANSWER — the selected tag, the typed # string, the checked items — on stderr, plus a button-specific exit status. So # this payload never has to scrape a repaint to know what happened. It runs each # widget with `2>` a file and `echo $?` to another, and 59-dialog.test reads both # off the disk afterwards. (58-nano.test had to reason its way to disk verdicts # because nano has no answer channel at all; here the answer channel is the # feature under test.) # # WHY A PTY IS STILL REQUIRED. Everything above the widgets — --version, # --create-rc — runs pre-curses and 59-dialog.test covers it directly. From # init_dialog() on, dialog needs a terminal: initscr(), $TERM resolved out of # /etc/terminfo, a TIOCGWINSZ that answers, and cbreak/noecho keystroke reads. # D1 isolates exactly that layer before any widget runs, so a terminal-side # failure is distinguishable from a widget-side one rather than reported as nine # broken widgets. # # THE DELIBERATE ESCAPE-SEQUENCE PROBE IS D6. Every other keystroke here is a # single byte (\r, \t, space); D6 sends the Down arrow, ESC [ B, and the verdict # is WHICH MENU TAG comes back. This substrate's timers are known to distort the # inter-byte gap ncurses uses to tell a lone ESC from the start of a sequence, so # the failure modes are all distinguishable in the answer: "two" means the # sequence decoded as one key; "one" means the cursor never moved; a nonzero exit # means the lone ESC was taken as cancel. The identical question 58-nano.test # asks through the disk, asked here through a return value. # # argv: — created by 59-dialog.test, which asserts what lands in it. # Emits D- OK/FAIL lines, then ALL-DIALOG-OK iff every session behaved. log_user 0 set env(TERM) "xterm" set env(DIALOGRC) "" set timeout 10 set work [lindex $argv 0] set pass 0 set total 0 # EVERY SESSION SIZES ITS OWN TERMINAL, and it has to. expect's stty_init is not # applied to the pty it allocates here: measured on target, a fresh spawn reports # whatever the controlling console is (50x160 under the local runner, something # else again in a browser xterm.js) rather than the 24x80 stty_init asks for. # Relying on it would make every geometry-dependent verdict a property of whoever # ran the suite. An explicit `stty rows 24 columns 80` inside the spawned shell # does take -- which is itself the D1 assertion below -- so it prefixes every # command and the widget layout is the same everywhere. set SIZE "stty rows 24 columns 80;" proc verdict {tag ok} { global pass total incr total if {$ok} { incr pass; puts "D-$tag OK" } else { puts "D-$tag FAIL" } } # Run one dialog session in a pty: start it, let it paint, send the keys, and # report whether the process exited without an expect-side error. WHAT it # answered is left to the .test, which reads $work/.out and .rc. proc session {name cmd keys settle} { global work SIZE spawn -noecho sh -c "$SIZE $cmd 2>$work/$name.out; echo \$? > $work/$name.rc" sleep $settle foreach k $keys { send -- $k; sleep 1 } expect { eof {} timeout {} } lassign [wait] _p _s _oserr _rc verdict $name [expr {$_oserr == 0}] } # --- D1: the terminal layer alone. No widget, no keystroke — a TIOCSWINSZ, the # TIOCGWINSZ that must answer with it, then initscr()/endwin(). # # It is written as a chain on purpose, so a failure names its own layer: sttysize # is the kernel's answer for the pty and maxsize is what CURSES made of the same # terminal (initscr() with $TERM resolved through /etc/terminfo, BRINTOS-67). # Both must read 24x80, and 59-dialog.test compares them to each other as well as # to the constant. A widget failure below with D1 green is a widget problem; D1 # red makes every later verdict uninteresting. spawn -noecho sh -c "$SIZE stty size >$work/sttysize.out 2>&1; dialog --print-maxsize 2>$work/maxsize.out; echo \$? > $work/maxsize.rc" expect { eof {} timeout {} } lassign [wait] _p _s _oserr _rc verdict D1maxsize [expr {$_oserr == 0}] # --- D2: inputbox, empty default, type a string, OK. The typed bytes must reach # the answer channel unmangled. session inputtyped \ "dialog --inputbox 'Enter text' 8 40" \ [list "brintos288" "\r"] 2 # --- D3: inputbox with a preset default, accepted untouched. Separates "the # widget echoed what I typed" from "the widget carried its own initial value". session inputdefault \ "dialog --inputbox 'Enter text' 8 40 preset-value" \ [list "\r"] 2 # --- D4/D5: yesno — the exit status IS the answer. Enter takes the focused Yes # (0); Tab moves focus to No and Enter takes it (1). Two sessions of one widget, # because a widget that always returned 0 would pass D4 alone. session yesyes "dialog --yesno 'Proceed?' 7 40" [list "\r"] 2 session yesno "dialog --yesno 'Proceed?' 7 40" [list "\t" "\r"] 2 # --- D6: THE ESCAPE-SEQUENCE PROBE (see header). Down then Enter must answer # "two" rather than "one". # # The byte sequence is ESC O B, not ESC [ B. dialog calls keypad(TRUE), which # emits smkx and puts the terminal in APPLICATION cursor-key mode, and xterm's # terminfo kcud1 is \EOB there — so \EOB is the sequence ncurses is matching # against, and it is what a real terminal driving this program sends. Measured, # not assumed: \E[B against this same binary leaves the lone ESC unmatched and # dialog exits 255 (cancel) with no answer at all. That is the same # ESC-disambiguation window 58-nano.test probes, entered from the other side. session menudown \ "dialog --menu 'Pick one' 12 40 3 one First two Second three Third" \ [list "\033OB" "\r"] 2 # --- D7: checklist — space toggles the focused item on, Enter returns the set. # The answer is a LIST, quoted, which is a different formatting path from the # single tag D6 returns. session checkspace \ "dialog --checklist 'Pick some' 12 40 3 one First off two Second off three Third off" \ [list " " "\r"] 2 # --- D8: gauge reads its percentages from STDIN and exits on EOF, with no # keystroke at all — the one widget here driven entirely by a pipe. It is also # the only session whose stdin is not the pty. spawn -noecho sh -c "$SIZE printf '10\\n50\\n100\\n' | dialog --gauge 'Working' 7 40 0 2>$work/gauge.out; echo \$? > $work/gauge.rc" expect { eof {} timeout {} } lassign [wait] _p _s _oserr _rc verdict D8gauge [expr {$_oserr == 0}] # --- D9: textbox pages a FILE through the widget — a read path none of the # others take — and Enter presses its Exit button. session textbox \ "dialog --textbox $work/pager.txt 12 40" \ [list "\r"] 2 puts "D-SUMMARY pass=$pass/$total" if {$pass == $total} { puts "ALL-DIALOG-OK" }