# selftest_hexedit.exp — drives hexedit over a REAL pty. # # hexedit has no batch mode, no --version and no --help that returns 0: every # path that does not DIE during argument parsing runs initCurses() and then sits # in `do display(); while (key_to_function(getch()));`. So a pty is not a nicety # here, it is the only way to reach the program at all — 58-hexedit.test can # cover the five diagnostics that exit before initscr() and nothing else. # # WORSE THAN UNREACHABLE: RUNNING IT ON A PIPE HANGS. `case ERR: break;` falls # through to `return TRUE`, so an EOF on stdin — which getch() reports as ERR — # spins the main loop forever at 100% CPU instead of exiting. That is why # 58-hexedit.test wraps every non-pty invocation in `timeout` and redirects # stdin, and why nothing outside this payload ever opens a file with hexedit. # # THE VERDICTS THAT MATTER ARE ON DISK, not on the screen (the same division # selftest_nano.exp settled on). A curses app can repaint a convincing lie, and # the bytes hexedit writes back through save_buffer() are the thing this port # actually has to get right. This payload types blind against settle delays and # emits one line per session; 58-hexedit.test reads the files afterwards and # decides. The four screen assertions kept below are each a SINGLE printw write # — the 8-digit offset column, the "-**" modified marker, and the two prompt # strings — because a needle spanning two of ncurses' writes has cursor-motion # escapes spliced through it and would never match. # # EVERY SESSION SIZES ITS OWN TERMINAL. expect's stty_init is not applied to the # pty on this target, and hexedit DIEs outright ("term is too small") rather than # adapting, so the `stty rows 24 columns 80` prologue is load-bearing exactly as # it is in selftest_bitwise.exp. It also fixes lineLength at 16 bytes, which is # what makes "byte 8 is on the first line" true for the goto case. # # argv: — created and pre-seeded by 58-hexedit.test, which asserts the # resulting files. # Emits H- OK/FAIL lines, then ALL-HEXEDIT-OK iff every case passed. log_user 0 set env(TERM) "xterm" set timeout 10 set SIZE "stty rows 24 columns 80;" set work [lindex $argv 0] set pass 0 set total 0 proc verdict {tag ok} { global pass total incr total if {$ok} { incr pass; puts "H-$tag OK" } else { puts "H-$tag FAIL" } } # Wait for the first full-screen paint. The offset column of line 0 is printed by # one PRINTW(("%08lX ", ...)) before any per-byte attribute change, so it is the # earliest contiguous run of bytes the pty can be matched on. proc await_render {} { expect { "00000000" { return 1 } timeout { return 0 } eof { return 0 } } } # --- S1: it opens a file, paints, and ^C leaves ------------------------------ # initscr() resolving $TERM=xterm out of /etc/terminfo and painting is already # most of what this port has to prove. raw() is in effect, so ^C is delivered as # a keystroke to getch() rather than as SIGINT — quit() runs and exits 0. spawn -noecho sh -c "$SIZE exec hexedit $work/view.bin" verdict H1render [await_render] send -- "\003" expect { eof {} timeout {} } lassign [wait] _p _s _oserr rc1 verdict H2quit [expr {$_oserr == 0 && $rc1 == 0}] # --- S2: hex-mode edit, ^W writes it back ------------------------------------ # The cursor starts at byte 0 with cursorOffset 0, so "4" sets the high nibble # and "1" the low one: byte 0 becomes 0x41. The status line's "---" turns to # "-**" the moment edited is non-NULL, which is the one repaint worth asserting # — it says the keystroke reached setTo() and not merely wgetch(). spawn -noecho sh -c "$SIZE exec hexedit $work/edit.bin" verdict H3render [await_render] send -- "4" sleep 1 send -- "1" # The needle is "**" and not the "-**" the status line reads, because refresh() # puts only CHANGED CELLS on the wire: the leading "-" is column 0 in both the # clean and the dirty form, so it is never re-emitted and a needle containing it # can never match. The two asterisks are adjacent changed cells and so arrive as # one run. "**" cannot appear before the edit either — the seed holds no 0x2A, so # the ASCII pane has no asterisk of its own to collide with. set dirty 0 expect { "**" { set dirty 1 } timeout { } eof { } } verdict H4dirtymark $dirty send -- "\027" sleep 1 send -- "\003" expect { eof {} timeout {} } lassign [wait] _p _s _oserr rc2 verdict H5saveexit [expr {$_oserr == 0 && $rc2 == 0}] # --- S3: ^X asks, and "n" discards ------------------------------------------- # The prompt is one displayOneLineMessage() write. Answering "n" runs # discardEdited() and then quit(), so keep.bin must come out byte-identical to # the copy 58-hexedit.test kept — the edit reached the buffer (S2 proves the # same keystrokes do) and was deliberately not written. spawn -noecho sh -c "$SIZE exec hexedit $work/keep.bin" verdict H6render [await_render] send -- "4" sleep 1 send -- "1" sleep 1 send -- "\030" set asked 0 expect { "Save changes (Yes/No/Cancel) ?" { set asked 1 } timeout { } eof { } } verdict H7savequery $asked send -- "n" expect { eof {} timeout {} } lassign [wait] _p _s _oserr rc3 verdict H8discardexit [expr {$_oserr == 0 && $rc3 == 0}] # --- S4: TAB toggles to ASCII entry ------------------------------------------ # In ASCII mode setTo() takes the key as the byte value rather than as a hex # digit, so one "Z" writes 0x5A where hex mode needed two keystrokes. Which of # the two ran is decided on disk: 0x5A means the toggle took, 0x0Z is not a byte # and a leftover hex mode would have made byte 0 start with the nibble 0 instead. spawn -noecho sh -c "$SIZE exec hexedit $work/ascii.bin" verdict H9render [await_render] send -- "\t" sleep 1 send -- "Z" sleep 1 send -- "\027" sleep 1 send -- "\003" expect { eof {} timeout {} } lassign [wait] _p _s _oserr rc4 verdict H10asciiexit [expr {$_oserr == 0 && $rc4 == 0}] # --- S5: ^G jumps, and the edit lands where it jumped to --------------------- # goto_char() prompts, ungetstr()s a "0x" prefix into the input queue and then # reads the rest with getnstr() under echo() — a second ncurses input path, and # the only consumer of it here. Typing "8" therefore means 0x8. The value written # is 0x43, which the seed has nowhere: if the jump had not happened the edit # would have landed at byte 0 and 58-hexedit.test would see 0x43 there instead of # the seed's 0x42, so "the cursor moved" and "the prompt was dismissed" cannot # produce the same file. spawn -noecho sh -c "$SIZE exec hexedit $work/goto.bin" verdict H11render [await_render] send -- "\007" # Without the trailing space of the real prompt, for the same changed-cells # reason: the cell after the "?" is a space before and after the repaint, so # refresh() drops it from the run and "New position ? " never arrives whole. set prompted 0 expect { "New position ?" { set prompted 1 } timeout { } eof { } } verdict H12gotoprompt $prompted send -- "8\r" sleep 1 send -- "4" sleep 1 send -- "3" sleep 1 send -- "\027" sleep 1 send -- "\003" expect { eof {} timeout {} } lassign [wait] _p _s _oserr rc5 verdict H13gotoexit [expr {$_oserr == 0 && $rc5 == 0}] puts "H-SUMMARY pass=$pass/$total" if {$pass == $total} { puts "ALL-HEXEDIT-OK" }