# selftest_joe.exp — drives JOE (Joe's Own Editor) over a REAL pty (BRINTOS-499). # # joe has no batch mode; it is a full-screen editor, so cooked stdin proves nothing # about the thing that is actually new here. This payload gives it a terminal and # types at it. # # WHY THE VERDICTS ARE ON DISK, NOT ON THE SCREEN. 58-nano.test measured this same # target and found that reading a curses app's ONGOING repaints back through the pty # is unreliable: the first burst (setup + status line + banner) arrives, and matching # anything painted after it times out even though the editor is demonstrably still # running and still obeying keystrokes. That is not currently understood well enough # to call a platform defect, and it does not need to be, because a screen scrape is # the WEAKER assertion anyway -- a curses app can repaint a convincing lie. So this # payload types blind against settle delays and lets 58-joe.test assert the BYTES # THAT REACHED THE DISK. # # WHERE THE SCREEN IS THE ONLY WITNESS, IT IS LOGGED AND GREPPED, NOT MATCHED HERE. # Two of this port's three questions are answered by the first paint and by nothing # else, and both are questions about what is ABSENT under one condition and present # under another -- which `expect` cannot state and grep -F can: # # * THE TERMINFO PAINTS (raw-joe-xterm / -linux / -dumb). joe paints from the entry # for $TERM, and paints differently for a different one: under `dumb` there is no # status banner at all, because that entry defines none of what the banner needs. # joe/termcap.c carries a COMPLETE termcap parser of its own as a fallback, so an # editor that painted one plausible screen would prove nothing by itself; two # different paints from two different entries is the database being read. The # same captures also carry the three expected reds for # brintos/linux-in-the-browser#152 — on this arm the termcap-name half of the # lookup returns NULL for the clear and standout strings, so xterm's ESC[2J and # ESC[7m never reach the screen. 58-joe.test names all of that. # # * THE PERSONALITY SCREENS (raw-jmacs / -jstar / -jpico / -rjoe). These four are # symlinks to the same binary. joe reads argv[0], loads /etc/joe/rc, and # the rc IS the key map -- so the banner it paints advertises different keys: # jmacs offers "^X ^C" to exit where joe offers "Ctrl-K Q", jstar's status line # offers "Ctrl-J for help" where joe's offers "Ctrl-K H", and jpico paints pico's # two-row key menu ("cut line") instead of a banner-and-status pair. The matching # DISK verdicts (^O writes under jpico, ^X^S writes under jmacs) are in the .test. # # Every capture session runs with the CWD set to the work directory and a bare # filename, because joe fits the file name, the cursor position, the clock and the # help key into one 80-column status line and drops the tail when the name is long. # A capture spawned with an absolute path loses the help key, and with it the only # thing that distinguishes jstar from joe. # # Keystrokes are single control bytes (^K ^X ^Y ^O ^S ^C) -- no timing ambiguity -- # EXCEPT the deliberate one: J4 sends the Down arrow, ESC [ B. That is the runtime's # stated risk (defective timers can distort the inter-byte gap a terminal reader uses # to tell a lone ESC from the start of a sequence), and it is measured through the # disk: after Down, ^Y must cut line 2 (bravo). If the sequence had not decoded, a # different line would go -- or "[B" would be typed into the buffer -- and 58-joe.test # names which. # # ^K is a PREFIX key: joe waits for the second byte. It is sent as its own send with a # settle delay after it, rather than as one two-byte string, so a slow repaint between # the two cannot swallow the second byte. # # argv: — created and pre-seeded by 58-joe.test, which asserts the resulting # files. Emits J- OK/FAIL lines, then ALL-JOE-OK iff every case # passed. log_user 0 set stty_init "rows 24 columns 80" set env(TERM) "xterm" set timeout 20 set work [lindex $argv 0] cd $work set pass 0 set total 0 proc verdict {tag ok} { global pass total incr total if {$ok} { incr pass; puts "J-$tag OK" } else { puts "J-$tag FAIL" } } # ^K , with the prefix given room of its own. proc ctlk {c} { send "\013" sleep 1 send $c } # --- J1/J9/J2: start on a new file, type two lines, save through the ^K D PROMPT, # leave with ^C. ^K D is used rather than the promptless ^K X on purpose: the # file-name prompt is its own line-editing code path, and it is the one a user meets # first. While the buffer is modified joe holds an edit lock -- a SYMLINK named # .# whose target is user@host.pid -- which J9 asserts mid-session, since # nothing outside the session can see it. spawn -noecho joe typed.txt set ok 0 expect { "Joe's Own Editor 4.6" { set ok 1 } timeout {} eof {} } verdict J1start $ok sleep 2 send "hello from joe\r" sleep 1 send "second line" sleep 2 verdict J9lockheld [expr {[llength [glob -nocomplain .#typed.txt]] == 1}] ctlk "d" sleep 2 send "\r" sleep 2 send "\003" expect { eof {} timeout {} } lassign [wait] _p _s _oserr rc1 verdict J2exit [expr {$_oserr == 0 && $rc1 == 0}] # --- J3: ^Y cuts the line the cursor is on (line 1) — a plain control byte. spawn -noecho joe cut.txt expect { "Joe's Own Editor 4.6" {} timeout {} eof {} } sleep 2 send "\031" sleep 2 ctlk "x" expect { eof {} timeout {} } lassign [wait] _p _s _oserr rc2 verdict J3cutexit [expr {$_oserr == 0 && $rc2 == 0}] # --- J4: the escape-sequence probe. Down arrow (ESC [ B) then ^Y. 58-joe.test asserts # that the three bytes were consumed as a SEQUENCE (no "[B" in the file) and # deliberately does not assert which line the ^Y took -- that outcome is not # reproducible on this arm, because joe decides an ESC is standalone with a timed # select(2) read and brintos/linux-in-the-browser#120 breaks select/poll timeouts. # The gap below is 8 s rather than 2 s: it did not settle the outcome, but it does # rule the delay out as the cause, and the .test says so. spawn -noecho joe arrow.txt expect { "Joe's Own Editor 4.6" {} timeout {} eof {} } sleep 3 send "\033\[B" sleep 8 send "\031" sleep 3 ctlk "x" expect { eof {} timeout {} } lassign [wait] _p _s _oserr rc3 verdict J4arrowexit [expr {$_oserr == 0 && $rc3 == 0}] # --- J5: jpico. ^O is pico's write-out; under plain joe ^O opens the options menu # and writes nothing at all, so the file this leaves behind can only exist if # argv[0]="jpico" selected /etc/joe/jpicorc. spawn -noecho jpico pico.txt expect { "cut line" {} timeout {} eof {} } sleep 2 send "pico line" sleep 2 send "\017" sleep 2 send "\r" sleep 2 send "\030" expect { eof {} timeout {} } lassign [wait] _p _s _oserr rc4 verdict J5picoexit [expr {$_oserr == 0 && $rc4 == 0}] # --- J6: jmacs. The same argument the other way: ^X^S / ^X^C are emacs bindings from # jmacsrc, and under joerc ^X is "next word" and ^S is a search prefix. spawn -noecho jmacs emacs.txt expect { "Joe's Own Editor 4.6" {} timeout {} eof {} } sleep 2 send "emacs style" sleep 2 send "\030\023" sleep 3 send "\030\003" expect { eof {} timeout {} } lassign [wait] _p _s _oserr rc5 verdict J6macsexit [expr {$_oserr == 0 && $rc5 == 0}] # --- J7: UTF-8 round trip. joe decodes the buffer to characters using its own # Unicode tables and re-encodes on save, so opening and writing back a file with 2-, # 3- and 4-byte sequences is a real decode/encode cycle rather than a copy. spawn -noecho joe utf8.txt expect { "Joe's Own Editor 4.6" {} timeout {} eof {} } sleep 3 ctlk "x" expect { eof {} timeout {} } lassign [wait] _p _s _oserr rc6 verdict J7utf8exit [expr {$_oserr == 0 && $rc6 == 0}] # --- J8: ^K F search. The search must MOVE THE CURSOR, which is asserted through the # ^Y that follows: bravo goes only if the match was found and the cursor landed on it. spawn -noecho joe find.txt expect { "Joe's Own Editor 4.6" {} timeout {} eof {} } sleep 2 ctlk "f" sleep 2 send "bravo\r" sleep 2 send "\r" sleep 2 send "\031" sleep 2 ctlk "x" expect { eof {} timeout {} } lassign [wait] _p _s _oserr rc7 verdict J8findexit [expr {$_oserr == 0 && $rc7 == 0}] # --- the logged first paints. The buffer is never modified, so joe leaves on its own # at eof/close without a save prompt; `close` is the belt-and-braces path for the # dumb entry, where there is no status line to confirm anything by. set timeout 6 proc capture {prog term tag file} { global work set ::env(TERM) $term log_file -a -noappend $work/raw-$tag.bin spawn -noecho $prog $file expect { timeout {} eof {} } catch { close } catch { wait } log_file } capture joe xterm joe-xterm ti1.txt capture joe linux joe-linux ti2.txt capture joe dumb joe-dumb ti3.txt capture jmacs xterm jmacs p1.txt capture jstar xterm jstar p2.txt capture jpico xterm jpico p3.txt capture rjoe xterm rjoe p4.txt puts "J-SUMMARY pass=$pass/$total" if {$pass == $total} { puts "ALL-JOE-OK" }