# selftest_dhex.exp — drives dhex's INTERACTIVE half over a REAL pty. # # dhex is unusual among curses editors in having a genuine batch mode, and # 58-dhex.test is mostly built on it: `-sa -w ` guards initscr() # out entirely, so the search engine, the offset arithmetic and the config parser # are all reachable from a pipe. What that leaves unproven is exactly what this # payload is for — that initscr() resolves $TERM out of /etc/terminfo, that the # screen paints, that keystrokes reach the buffer, and that savechanges() writes # the edited bytes back. A build whose ncurses was a stub would pass every batch # case in the suite and fail every case here. # # EVERY SESSION PASSES -f. With a config named on the command line, main() never # looks at $HOME and readconfigfile() finds all 23 special keys already # configured, so neither the keyboard-setup wizard nor a writable home is on the # path. 58-dhex.test asserts the $HOME behaviour separately, where it belongs. # # THE VERDICTS THAT MATTER ARE ON DISK, not on the screen — the same division # selftest_hexedit.exp and selftest_nano.exp settled on. savechanges() reopens the # file "r+b" and fwrites ONE BYTE per recorded change at its own offset, so a # whole-buffer rewrite and a page written to the wrong place are both visible in # the file afterwards; this payload types blind against settle delays and emits # one line per session, and 58-dhex.test reads the files and decides. # # The screen assertions kept below are each a SINGLE contiguous write. The # function-key bar is the safest of them: printmainmenu() writes each label whole, # the labels are constant across files, and "Corr." appears in the DIFF bar only — # where "Goto" and "Search" are blanked — so one needle tells the two modes apart. # A needle spanning two of ncurses' writes has cursor-motion escapes spliced # through it and would never match. # # EVERY SESSION SIZES ITS OWN TERMINAL, as in selftest_hexedit.exp and # selftest_bitwise.exp: expect's stty_init is not applied to the pty on this # target, and dhex reads COLS/LINES at paint time to place the panes. # # argv: — the work dir is created and pre-seeded by # 58-dhex.test, which asserts the resulting files. # Emits D- OK/FAIL lines, then ALL-DHEX-OK iff every case passed. log_user 0 set env(TERM) "xterm" set timeout 15 set SIZE "stty rows 24 columns 80;" set work [lindex $argv 0] set rc [lindex $argv 1] set pass 0 set total 0 set F10 "\033\[21~" proc verdict {tag ok} { global pass total incr total if {$ok} { incr pass; puts "D-$tag OK" } else { puts "D-$tag FAIL" } } # Wait for the function-key bar, which printmainmenu() writes last on every full # paint — so matching it means the header, both panes and the offset column all # went out before it. proc await_bar {needle} { expect { $needle { return 1 } timeout { return 0 } eof { return 0 } } } # dhex prints welcomescreen() to stderr AFTER endwin(), so this banner is the # clean-exit signature of the interactive path and cannot be produced by a crash. proc await_exit {} { set seen 0 expect { "*** DHEX" { set seen 1; exp_continue } eof { } timeout { } } lassign [wait] _p _s _oserr _rc return [expr {$_oserr == 0 && $_rc == 0 && $seen == 1}] } # --- S1: it opens a file, paints, and F10 leaves ------------------------------ # No edits, so main()'s `if (buf1->changesnum)` is false and F10 exits without a # save dialog. This is the case that says initscr() + terminfo + the paint work. spawn -noecho sh -c "$SIZE exec dhex -f $rc $work/view.bin" verdict D1render [await_bar "Goto"] send -- $F10 verdict D2quit [await_exit] # --- S2: hex entry at byte 0, saved ------------------------------------------- # windowfield 0 is the hex pane and the cursor starts at byte 0, so "4" sets the # high nibble and "1" the low one and the change is recorded on the SECOND digit. # 0x41 is a value the seed does not hold at byte 0 (it holds 'B', 0x42), so a # keystroke that never reached the buffer cannot produce it. spawn -noecho sh -c "$SIZE exec dhex -f $rc $work/edit.bin" verdict D3render [await_bar "Goto"] send -- "4" sleep 1 send -- "1" sleep 1 send -- $F10 # savedialog() draws its frame title as one write, and the menu items after it. verdict D4savequery [await_bar "Save changes?"] # The hotkey both selects and confirms: MenuInteract() turns a matching hotkey # into KEYENTER in the same pass, so no separate Enter is sent. send -- "y" verdict D5saveexit [await_exit] # --- S3: the same edit, answered "No" ----------------------------------------- # "No" returns RETOK without calling savechanges(), so keep.bin must come out # byte-identical to the seed. S2 proves these keystrokes do reach the buffer, so # an unchanged file here means the discard was honoured rather than that nothing # was typed. spawn -noecho sh -c "$SIZE exec dhex -f $rc $work/keep.bin" verdict D6render [await_bar "Goto"] send -- "4" sleep 1 send -- "1" sleep 1 send -- $F10 verdict D7discardquery [await_bar "Save changes?"] send -- "n" verdict D8discardexit [await_exit] # --- S4: TAB switches to the ASCII pane --------------------------------------- # In windowfield 1 the key IS the byte, so one "Z" writes 0x5A where the hex pane # needed two keystrokes. Which pane was active is decided on disk: a leftover hex # mode would have taken "Z" for no digit at all and written nothing. spawn -noecho sh -c "$SIZE exec dhex -f $rc $work/ascii.bin" verdict D9render [await_bar "Goto"] send -- "\t" sleep 1 send -- "Z" sleep 1 send -- $F10 verdict D10asciiquery [await_bar "Save changes?"] send -- "y" verdict D11asciiexit [await_exit] # --- S5: -od puts the cursor somewhere else, and the edit lands there --------- # The same option the batch leg uses to rotate the search order also seeds the # INTERACTIVE cursor, which is the only place the two halves of this port meet. # 0x43 is written at byte 8; the seed holds 0x33 there and 0x42 at byte 0, so # "the cursor moved" and "the option was ignored" produce different files. spawn -noecho sh -c "$SIZE exec dhex -f $rc -od 8 $work/goto.bin" verdict D12render [await_bar "Goto"] send -- "4" sleep 1 send -- "3" sleep 1 send -- $F10 verdict D13gotoquery [await_bar "Save changes?"] send -- "y" verdict D14gotoexit [await_exit] # --- S6: undo takes the change back before the dialog can be reached ---------- # F9 (and 'u' in the hex pane) decrements changesnum, so F10 finds nothing to save # and exits WITHOUT a dialog. The verdict is the exit itself plus the untouched # file: if undo had not run, this session would still be sitting at "Save # changes?" when the timeout fires. spawn -noecho sh -c "$SIZE exec dhex -f $rc $work/undo.bin" verdict D15render [await_bar "Goto"] send -- "4" sleep 1 send -- "1" sleep 1 send -- "u" sleep 1 send -- $F10 verdict D16undoexit [await_exit] # --- the two sessions whose first paint exceeds 4 KiB ------------------------- # Everything above paints ~3.4 KB and lands in one piece. Diff mode (~4.9 KB) and # the hexcalc (~5.8 KB) do not, and on this target a single write() to a pty # larger than 4095 bytes silently loses its tail -- measured 5 of 12 identical # hexcalc paints truncated at exactly 4095 bytes, and 10 of 10 for a plain # `cat` of an 8000-byte file, while 2000 small writes totalling 10.9 KB never # lose a byte. ncurses flushes a refresh as one write, so the bar -- written last # by printmainmenu() -- is what goes missing, and the session is otherwise # healthy (its exit case passes either way). # # Retrying the spawn is the honest workaround: the paint is either whole or # short, never wrong, so a repeat that lands whole proves the same thing the # first one would have. It is bounded so a genuine render regression still fails. # # BRINTOS-WORKAROUND: unfiled revisit — the guest tty drops everything past 4095 # bytes of a single write with no short-write signal; when that is fixed these # two renders can go back to a single attempt like every other session here. # Unfiled because no brintos.io credential was available to report it from; # BRINTOS-754 tracks getting it upstream, and this marker takes the issue id # once it has one. # The retry is written out at top level rather than wrapped in a proc: `spawn` # binds spawn_id in the CALLING scope, so a re-spawn inside a proc is invisible # to await_bar/await_exit here and the following `send` would address a closed # process. # # --- S7: diff mode paints both buffers ---------------------------------------- # Two files means two tBuffers, each carrying a 1 MB data[] and a 1 MB changes[] # inline, so this session is also the largest single allocation the port makes on # this image. "Corr." is in the diff key bar only -- F1/F2 are blank there -- so # matching it proves printbufferdiff() ran rather than printbuffersingle(). set DIFFCMD "$SIZE exec dhex -f $rc $work/view.bin $work/view2.bin" set got 0 for {set i 1} {$i <= 4} {incr i} { set timeout 15 spawn -noecho sh -c $DIFFCMD if {[await_bar "Corr."]} { set got 1; break } catch { send -- $F10 } set timeout 5 catch { expect eof } } verdict D17diffrender $got send -- $F10 verdict D18diffexit [await_exit] # --- S8: -x is the hexcalc, and it opens with NO file at all ------------------ # The one path where main() skips openbuf() entirely, so it is also the only case # that says the curses layer comes up without a buffer behind it. hexcalc.c is # otherwise unreachable from this suite: F5 reaches it from the editor, and # nothing reaches it from the batch leg. It exits on a literal "x", not F10. set CALCCMD "$SIZE exec dhex -f $rc -x" set got 0 for {set i 1} {$i <= 4} {incr i} { set timeout 15 spawn -noecho sh -c $CALCCMD if {[await_bar ":Add"]} { set got 1; break } catch { send -- "x" } set timeout 5 catch { expect eof } } verdict D19calcrender $got send -- "x" verdict D20calcexit [await_exit] puts "D-SUMMARY pass=$pass/$total" if {$pass == $total} { puts "ALL-DHEX-OK" }