#+TITLE: Zutty developer guide#+OPTIONS: author:nil timestamp:nil num:nil val:nil html-style:nil H:3 ^:{}#+HTML_HEAD: <link rel="stylesheet" type="text/css" href="org.css"/> * Hacking on Zutty** Compiling for development or debugging If you plan to spend time hacking on Zutty, you should first compile adebug build of it. The purpose of this is to compile in trace loggingoutput, as well as debugging facilities. To configure the build for this development mode, add the option=--debug= to the configuration: : ./waf configure --debug Feel free to add any other options you usually use for the normalbuild. Confirm that you see this line in the output: : Debug build : yes Then, compile Zutty as usual: : ./waf [-j <JOBS>] The build will produce an executable named =zutty.dbg= (as opposed to=zutty= for normal builds). This allows you to keep a normal build anda debug build from the same source side by side each other, whichmight prove useful while working on some debugging-intensive problem. Normally, you will not want to install the development buildsystem-wide. Run it from an existing terminal as =build/src/zutty.dbg-v= (plus any other options) to receive verbose debugging logs in theconsole. As stated in the [[./USAGE.org][User guide]], by default (if neither =-quiet= nor=-verbose= is given), Zutty will print errors and warnings, but notinfo messages. Those have to be enabled with =-verbose=, and in caseof a debug build, that will enable Trace messages as well. ** The Step Debugger A debug build of Zutty has compiled-in support not just for generatinglots of trace output, but also for a facility called the StepDebugger. This is a facility specifically built to enable closeinspection of the virtual terminal in action. The Step Debugger is initially dormant; it can be activated bypressing the "debug key", which is hardwired to PrintScreen (but thehook for it is only compiled in the debug build, so it does not affectthe PrintScreen key in a normal build). When the Step Debugger is activated, the Zutty process will suspenditself on the completion of every N-th escape sequence (i.e., when thestate machine is set back to "normal text input"), with N cycledthrough 1, 10, 100 and zero (off) on each additional press of thePrintScreen key. Move focus to the Zutty window, press PrintScreen, and confirm thatyou see this line in the log output: : T [vterm.icc: 40] *** DEBUG step=1 The =step=1= communicates that going forward, Zutty will suspenditself on every completed escape sequence. To test this, typesomething (I am typing the letter =k=) and then hit Backspace. Thiswill trigger the Debug Stop, and the log will look similar to this: : T [vterm.icc:366] pty write (mod=0): 'k' (1 bytes): T [vterm.icc:400] pty read: 'k' (1 bytes): T [vterm.icc:617] hideCursor [] p(0,48) d(42,103) mgn[0,42) hmgn:0 [0,103): T [vterm.icc:108] Inserted: 'k' (1 bytes): T [vterm.icc:605] showCursor [] p(0,49) d(42,103) mgn[0,42) hmgn:0 [0,103): T [vterm.icc:389] pty write: '\x7f' (1 bytes): T [vterm.icc:400] pty read: '\b\e[K' (4 bytes): T [vterm.icc:617] hideCursor [] p(0,49) d(42,103) mgn[0,42) hmgn:0 [0,103): T [vterm.icc:914] csi_CUB [] p(0,49) d(42,103) mgn[0,42) hmgn:0 [0,103): T [vterm.icc:1144] csi_EL [0] p(0,48) d(42,103) mgn[0,42) hmgn:0 [0,103): T [vterm.icc: 50] *** DEBUG STOP (step=1), 4 bytes since last:: '\b\e[K' (4 bytes): T [vterm.icc: 55] Issue 'kill -CONT 5429' or 'fg' to continue. Following the trace log, we can see that first the keypress was sentto the shell (=pty write=), then the echoed character was read backfrom the shell (=pty read=), which triggered an insertion of =k= astext. The screen-updating is surrounded by hiding and showing thecursor. Then, as a consequence of the Backspace keypress, Zutty sendsthe =DEL= character (=\x7f=) to the shell, which echoes back =\b\e[K=:first =\b=, a backspace character, followed by =\e[K=, or a "CSI K"sequence in VT100-speak, which instructs Zutty to clear the terminalscreen from the cursor to the end of line. Since this constitutes afinished escape sequence, this is the point where the Debug Stopoccurs. As part of processing the input stream from the terminal, Zuttyexecuted the functions =csi_CUB= (interpreting =\b=) and =csi_EL=(interpreting =\e[K=). The former stands for Cursor Back, the latterfor Erase Line -- these are the names of the requested screenoperations according to the VT100-series standards. The argumentsparsed from the input escape sequences, effective at the time of thefunction call, are visible in square brackets. The single argumentzero (shown as =[0]=) passed to the Erase Line routine is the implicitdefault; the actual escape sequence did not contain a numericargument. Also worth noting is that on each trace point (such as =hideCursor=,=showCursor=, =csi_CUB=, etc.) several state variables are printed.These include the current cursor position =(0,49)=, the dimensions=(42,103)=, and the scrolling area top and bottom margins =[0,42)=(the mismatched brackets denote that the top row of scrolling isinclusive of the scrolling range, while the bottom row is exclusive). To resume Zutty, send a continue signal (SIGCONT) via kill, or justtype =fg= into the terminal running Zutty. The latter is a neat trickthat relies on the parent terminal detecting that the child processhas been stopped, and putting it into the background. When trying to understand how the virtual terminal processes a longerinput sequence, it becomes tedious to step through each escapesequence one by one. Pressing PrintScreen will cycle through thenumber of debug steps to go between each stop: first 1, then 10, 100,then 0 (the Step Debugger is turned off), then back to 1, 10, 100,then off again, etc. While doing this, you will see this in the outputlog: : T [vterm.icc: 40] *** DEBUG step=10: T [vterm.icc: 40] *** DEBUG step=100: T [vterm.icc: 40] *** DEBUG step=0: T [vterm.icc: 40] *** DEBUG step=1: T [vterm.icc: 40] *** DEBUG step=10: T [vterm.icc: 40] *** DEBUG step=100: ... ** Automated testing By their very nature, graphical terminal emulators are interactiveprograms. Therefore, testing them in an automated fashion (e.g., forregression testing) can be tricky. We employ a method suitable as a general means to automation, as it isindependent of the terminal under test: it does not require modifyingthe program by e.g., implementing test hooks to inject events or reportscreen content. This allows us to include several established terminalemulators in the test along with Zutty to research the state of theart and see how different programs stack up against each other. Prerequisites:- imagemagick (for convert & identify), wmctrl, xvkbd : apt-get install imagemagick wmctrl xvkbd On a high level, testing consists of these steps:- Start the terminal (the unit under test) as a subprocess and note its pid- Obtain its X window id: : wmctrl -lp | grep <pid> | awk '{print $1}'- use =xvkbd= to send events to the window: : xvkbd -window <id> -no-jump-pointer -text "\D3\{+1}\D3\{-1}\D3\{+Return}\D3\{-Return}\D3" Note the explicit keysym presses and releases, plus the interleaved delays. For reference, see: http://t-sato.in.coocan.jp/xvkbd/- Make a screenshot of the window via the window id : xwd -nobdrs -id <id> | convert xwd:- png:- > <shot-name>.png- Generate a signature of the screen content, to be compared against a reference value: : identify -verbose <shot-name>.png | grep signature | awk '{print $2}' | cut -32 (We cut the hash in half to make it less unwieldy.) Having consistent hashes that depend only on the rendered pixel image is convenient, as we do not need to store the reference images themselves beside our test script. The above steps are automated by some fairly straightforward bashscripts under the =test/= subdirectory. These scripts all source the=testbase.sh= script, which constitutes the test library. *** CAVEATs While Zutty itself compiles and runs on Linux as well as BSDplatforms, the test toolkit assumes a specific development environment(most notably: Linux platform with =bash= as the default shell, andthe presence of a window manager with certain standard capabilities). Please also note that the reference signatures of screen captures arevalid for ImageMagick version 6 only; version 7 has altered the imagesignature hash algorithm, which breaks the hashes included in thetests. *** Anatomy of a test script Each executable script under =test/= is an individually runnable testsuite. It is written as a plain old bash script, sourcing the=testbase.sh= test code library and using its facilities. For example,=truecolor.sh= is a very simple test script to test support for settingcolor attributes to truecolor (24 bit) values. The full script isreproduced below: #+BEGIN_SRC sh#!/usr/bin/env bash cd $(dirname $0)source testbase.sh IN "source truecolor_inc.sh\r"SNAP truecolor_01 33a31e4d3b9fbe486c27b01764dc1823#+END_SRC The script starts by declaring itself as a shell script, then settingup its working directory to be the location of the script (aconvenience to make relative file paths work in later parts of thescript, independent of the location the script was invoked from). Then=testbase.sh= is sourced. The actual test code is just two lines, starting with the commands=IN= and =SNAP=. These are invocations of functions defined in=testbase.sh= and execute in the environment set up by sourcing thatfile. =IN= will send the specified string, as keyboard input, to theterminal under test. In our example, the shell running in the terminalwill source the file =truecolor_inc.sh= that contains some setup code(not reproduced here) to make a certain pattern appear on theterminal. Note the trailing =\r= that will result in a virtual =Enter=keypress. The subsequent =SNAP= will capture the resulting terminal windowcontent under the name =truecolor_01=, generate a signature (hash) ofit, and compare that with the supplied value. If there is a match, theoutput is verified to be correct; else a test failure is reported. The snap name is used to save the captured window image under=test/output/<profile>=. This is useful for later inspection of testresults. With the default profile (see [[Test profiles]] below), theoutput of this test will be saved as=test/output/zutty/truecolor_01.png=. There are some other useful functions exported by the test framework,e.g.: =CHECK_DEPS=, =CHECK_FILES=, and =WAIT_FOR_DOT_COMPLETE=. If youencounter them in test script code, it is best to look directly in=testbase.sh= for their implementation. Note that starting and stopping of the terminal under test is done aspart of the test framework and nothing is explicitly written in thetest script. See [[Test profiles]] below on how to control the details ofthis process. *** Common test script options Test scripts similar to the one shown above (building on=testbase.sh=) all take a uniform set of command line options. Allarguments are optional, below defaults are in effect for omitted ones.Syntax: =--<arg-name>=<arg-value>=; value defaults to "yes" ifomitted. : Option Default: --------------------------: --ci-mode no: --debug no: --profile zutty: --step no: --update-sig no **** =--step= The =--step= option can be given without argument, in which case itwill be equivalent to =--step=yes=, or given as =--step=new=. Theformer one will result in step mode, which will pause immediatelyafter each snapshot is taken (the terminal under test still displayingthis output, allowing visual inspection), and display a prompt: : [S]tep / [N]ew only / [C]ontinue / [Q]uit (s/n/c/q) ? This allows the user to choose how to proceed:- *S* - continue stepping, i.e., stop after each snapshot- *N* - continue without stopping, except on new snapshots- *C* - continue without ever stopping again- *Q* - quit the test The =new= option is useful when developing a test suite. It will runthe test script forward until a =SNAP= command without a verificationhash is found. **** =--update-sig= Enabling =--update-sig= will result in a prompt on a verificationfailure, i.e., when the =SNAP= command captures a screenshot with adifferent hash than the reference stored in the script: : Update signature: [y]es / [N]o / [a]ll ? By answering *Y* here, the signature in the script will be updated.By answering *A*, all future differences will also be updated withouta further prompt. This is useful in case the behaviour of Zutty ischanged in a way that alters its output; in case it is establishedthat the new output is "more correct" than the previous one; and wewant to adapt the tests to verify against this new output in thefuture. Use this with a great deal of caution. It is recommended to use *Y* infavour of *A* and before answering each prompt, to do a careful visualinspection of each screen for correctness. **** =--profile= See [[Test profiles]] below. **** =--ci-mode= The =--ci-mode= option sets up the test script to execute in anunattended manner, suitable for automated testing. Step mode is turnedoff (overriding =--step=), signature updates are turned off(overriding =--update-sig=), and the script is set up to immediatelyexit with a nonzero code on a verification failure. **** =--debug= The =--debug= option sets the variable =DEBUG= to =yes= (instead ofits default value =no=). This is used by the =zutty= test profile toensure that the correct UUT executable (=zutty= vs. =zutty.dbg=) islaunched. This option is currently not used by any other profile, norfor any other, more general purpose. *** Correctness tests The list of correctness tests (automatically run in sequence by[[The CI test script]]): - =keys.sh=: Keyboard input handling (see [[./KEYS.org][Key mapping sequences]] for further documentation). Note: This test might fail if your computer is configured to use a non-US keyboard.- =nonascii.sh=: Test displaying non-ascii (double-width and other exotic) characters, based on the Emacs 'hello' file (=M-x view-hello-file=)- =scrollback.sh=: Scrollback (page history) support- =title.sh=: Setting the window title from within the terminal via escape sequences- =truecolor.sh=: True color support- =utf8.sh=: UTF-8 support (based on the [[../test/UTF-8-test.txt][UTF-8 decoder capability and stress test]] by Markus Kuhn)- =vttest.sh=: VTTEST screens. Note: This suite depends on a specific version of =vttest=, and will complain if the version found does not match. Just run the vttest install script mentioned by the error message, and you should be good to go.- =wraptest.sh=: Additional tests for verifying the correctness of last-column/pending-wrap subtleties. Note: This suite depends on the [[https://github.com/mattiase/wraptest][wraptest]] program (not bundled with the zutty source repository). Just run the install script mentioned by the error message you get on the first run, and you should be good to go. Apart from running all the tests via [[The CI test script]] (which youshould routinely run during development, and especially before sendinga patch or opening a pull request), it is also possible to run any ofthe above tests manually. For example, to run the test automating atraversal of Vttest's menu system (and in case of running againstZutty, also verifying that results are as expected): : test/vttest.sh Do not forget about the [[Common test script options]] above; those becomeuseful during development (both of Zutty itself and the test suites).For example, to run the above test step-by-step (stopping at eachimage checkpoint): : test/vttest.sh --step *** Font rasterization tests A separate test =fonts.sh= exists that is very similar to thecorrectness tests above, but is not part of [[The CI test script]]. Thisrenders a captured hardcopy of a terminal-intensive program (BpyTOP)exercising many features (chiefly, Unicode line drawing glyphs) thatare helpful in spotting errors in the font loading and rasterizationprocess employed by Zutty, and validates the result across a matrix ofseveral font faces and sizes. Please read the commentary in the testscript for the fonts required to run the test. *** Performance tests The performance of Zutty can be verified with the below tests. Theseare to be run manually (similar to how you run any of the[[Correctness tests]] on an ad-hoc basis). The goal of these tests is toget a handle on performance under repeatable circumstances. Since everyone's hardware and systems are different, it does not maketoo much sense to compare numbers obtained by different people atdifferent times. Rather, it is most useful to compare the resultsobtained on the same system (running the comparison tests after oneanother without any changes to the rest of the system), with the goalof comparing a proposed set of patches to the baseline of Zutty, or tocompare Zutty with another terminal emulator. Needless to say: when running the below scripts, your machine shouldbe otherwise idle. While correctness tests should ideally be run on both the debug build(do this by passing =--debug= to the test scripts) and the normalbuild, it does not make sense to run performance tests on the debugbuild. The results would be meaningless due to excessive logging andhitting other debug-only code paths. Current performance tests: - =cat_dict.sh=: Arguably the dumbest possible performance test of any text terminal, this test consists of outputting a very long text file containing (mostly) very short lines with English words, one per line. This test will be repeated a number of times (check =TIMES= in the script) and the overall timing and data throughput will be computed at the end. Since the input does not contain any terminal controls (escape sequences), it is a measure of the raw incoming data rate the terminal can sustain while frequently forced to scroll/page its output. This load resembles one extreme end of the way a terminal can be used. - =cat_vtscript.sh=: This test generates load resembling the other extreme end of possible terminal usage, by outputting the stream of data written to the terminal in the course of the VTTEST cycle (see =vttest.sh= among the [[Correctness tests]]). However, instead of verifying the correctness of the generated screen output, here we are interested in the performance of processing the input stream heavy on all kinds of escape sequences. Screen updates are dominated by intra-screen rewrites and relatively little scrolling/paging activity is forced. Similar to the =cat_dict.sh= test, the input is fed into the terminal a number of times, and overall timing and throughput is measured and calculated. *** The CI test script The script =test/run_ci.sh= will run all automated [[Correctness tests]]in sequence with the =--ci-mode= option, stopping and exiting with anonzero return code if any of them exits with an error, and concludingwith a confirmatory message and zero return code otherwise. You should always run this at a convenient time (it will occupy yourscreen for about 25 minutes) and observe the successful result beforesending a patch with your changes. For extra confidence, build Zuttyboth with and without debug, and run the CI script both with andwithout the =--debug= option. If you have changed anything related to font loading or rasterization(=font.cc= or =fontpack.cc=), make sure you run the [[Font rasterizationtests]] as well. Please read the commentary in the test script for morecontext. *** Test profiles Given that our method of testing is independent of the terminal itself(meaning that we do not rely on any hooks or test instrumentation inthe terminal itself), we can run the same tests against otherterminals, too. This is useful for comparison and research purposes.The default profile is =zutty=, invoking the locally built Zuttyexecutable as the unit under test. Profiles are defined as shell include files under =test/profiles=, andcan be invoked by passing the =--profile= option to the testscript. For example, to run the VTTEST suite against =xterm=: : ./test/vttest.sh [--step] --profile=xterm Available profiles can be enumerated by looking in =test/profiles/=(pass their name without the =.sh= extension to =--profile=, asabove). The list is also printed by the test script in case an invalidprofile name is given. Each test profile contains a program invocation assigned to =UUT_EXE=,used to launch the terminal under test with the right arguments. Theterminal must be configured so that its geometry is 80 characters wideand 24 rows tall. Some other variables set up in the profile encode differentcapabilities to control the test scripts so that unsupported featuresare skipped (and the terminal does not encounter confusing escapesequences). Examples: =MISSING_ANSWERBACK=, =MISSING_DSR=,=MISSING_SECONDARY_DA=, =SUPPORTS_VT52=, =SUPPORTS_VT220=. Pleasecheck the script files for details. Note that validation against the stored image hashes does not makesense, unless preparations are made to ensure that the terminalsettings (size, font, etc.) are perfectly identical. Even then,differences from the valid output stored for Zutty do not necessarilyconstitute bugs in any other terminal, unless the rendered screencontent is visibly wrong. For this reason, auto-validation of thesaved screens runs in a relaxed mode for all profiles other than=zutty=. This means that for terminals other than Zutty, matches arestill prominently displayed (by a line reading =MATCH= in green),while non-matches are considered normal and indicated by =DONE=,followed by a metric of the image difference compared to thereference. The output image and the image generated for Zutty will bediffed, resulting in a difference image that will highlightdifferences with red. ** Contribution guide There is no commercial entity behind Zutty. It is a volunteer effortwith extremely limited resources. Your contributions will be dulyconsidered, but please discuss your plans with us before starting out.Otherwise, you run the risk of being ignored after having put time andeffort into the preparation of a patch. Not every aspect of what makes a good contribution can be codified (webelieve having good taste plays an important role), but as a minimum,please keep the following in mind: *** Respect existing coding style Lines 1–500 of 1028. Load more
#+TITLE: Zutty developer guide #+OPTIONS: author:nil timestamp:nil num:nil val:nil html-style:nil H:3 ^:{} #+HTML_HEAD: <link rel="stylesheet" type="text/css" href="org.css"/> * Hacking on Zutty ** Compiling for development or debugging If you plan to spend time hacking on Zutty, you should first compile a debug build of it. The purpose of this is to compile in trace logging output, as well as debugging facilities. To configure the build for this development mode, add the option =--debug= to the configuration: : ./waf configure --debug Feel free to add any other options you usually use for the normal build. Confirm that you see this line in the output: : Debug build : yes Then, compile Zutty as usual: : ./waf [-j <JOBS>] The build will produce an executable named =zutty.dbg= (as opposed to =zutty= for normal builds). This allows you to keep a normal build and a debug build from the same source side by side each other, which might prove useful while working on some debugging-intensive problem. Normally, you will not want to install the development build system-wide. Run it from an existing terminal as =build/src/zutty.dbg -v= (plus any other options) to receive verbose debugging logs in the console. As stated in the [[./USAGE.org][User guide]], by default (if neither =-quiet= nor =-verbose= is given), Zutty will print errors and warnings, but not info messages. Those have to be enabled with =-verbose=, and in case of a debug build, that will enable Trace messages as well. ** The Step Debugger A debug build of Zutty has compiled-in support not just for generating lots of trace output, but also for a facility called the Step Debugger. This is a facility specifically built to enable close inspection of the virtual terminal in action. The Step Debugger is initially dormant; it can be activated by pressing the "debug key", which is hardwired to PrintScreen (but the hook for it is only compiled in the debug build, so it does not affect the PrintScreen key in a normal build). When the Step Debugger is activated, the Zutty process will suspend itself on the completion of every N-th escape sequence (i.e., when the state machine is set back to "normal text input"), with N cycled through 1, 10, 100 and zero (off) on each additional press of the PrintScreen key. Move focus to the Zutty window, press PrintScreen, and confirm that you see this line in the log output: : T [vterm.icc: 40] *** DEBUG step=1 The =step=1= communicates that going forward, Zutty will suspend itself on every completed escape sequence. To test this, type something (I am typing the letter =k=) and then hit Backspace. This will trigger the Debug Stop, and the log will look similar to this: : T [vterm.icc:366] pty write (mod=0): 'k' (1 bytes) : T [vterm.icc:400] pty read: 'k' (1 bytes) : T [vterm.icc:617] hideCursor [] p(0,48) d(42,103) mgn[0,42) hmgn:0 [0,103) : T [vterm.icc:108] Inserted: 'k' (1 bytes) : T [vterm.icc:605] showCursor [] p(0,49) d(42,103) mgn[0,42) hmgn:0 [0,103) : T [vterm.icc:389] pty write: '\x7f' (1 bytes) : T [vterm.icc:400] pty read: '\b\e[K' (4 bytes) : T [vterm.icc:617] hideCursor [] p(0,49) d(42,103) mgn[0,42) hmgn:0 [0,103) : T [vterm.icc:914] csi_CUB [] p(0,49) d(42,103) mgn[0,42) hmgn:0 [0,103) : T [vterm.icc:1144] csi_EL [0] p(0,48) d(42,103) mgn[0,42) hmgn:0 [0,103) : T [vterm.icc: 50] *** DEBUG STOP (step=1), 4 bytes since last: : '\b\e[K' (4 bytes) : T [vterm.icc: 55] Issue 'kill -CONT 5429' or 'fg' to continue. Following the trace log, we can see that first the keypress was sent to the shell (=pty write=), then the echoed character was read back from the shell (=pty read=), which triggered an insertion of =k= as text. The screen-updating is surrounded by hiding and showing the cursor. Then, as a consequence of the Backspace keypress, Zutty sends the =DEL= character (=\x7f=) to the shell, which echoes back =\b\e[K=: first =\b=, a backspace character, followed by =\e[K=, or a "CSI K" sequence in VT100-speak, which instructs Zutty to clear the terminal screen from the cursor to the end of line. Since this constitutes a finished escape sequence, this is the point where the Debug Stop occurs. As part of processing the input stream from the terminal, Zutty executed the functions =csi_CUB= (interpreting =\b=) and =csi_EL= (interpreting =\e[K=). The former stands for Cursor Back, the latter for Erase Line -- these are the names of the requested screen operations according to the VT100-series standards. The arguments parsed from the input escape sequences, effective at the time of the function call, are visible in square brackets. The single argument zero (shown as =[0]=) passed to the Erase Line routine is the implicit default; the actual escape sequence did not contain a numeric argument. Also worth noting is that on each trace point (such as =hideCursor=, =showCursor=, =csi_CUB=, etc.) several state variables are printed. These include the current cursor position =(0,49)=, the dimensions =(42,103)=, and the scrolling area top and bottom margins =[0,42)= (the mismatched brackets denote that the top row of scrolling is inclusive of the scrolling range, while the bottom row is exclusive). To resume Zutty, send a continue signal (SIGCONT) via kill, or just type =fg= into the terminal running Zutty. The latter is a neat trick that relies on the parent terminal detecting that the child process has been stopped, and putting it into the background. When trying to understand how the virtual terminal processes a longer input sequence, it becomes tedious to step through each escape sequence one by one. Pressing PrintScreen will cycle through the number of debug steps to go between each stop: first 1, then 10, 100, then 0 (the Step Debugger is turned off), then back to 1, 10, 100, then off again, etc. While doing this, you will see this in the output log: : T [vterm.icc: 40] *** DEBUG step=10 : T [vterm.icc: 40] *** DEBUG step=100 : T [vterm.icc: 40] *** DEBUG step=0 : T [vterm.icc: 40] *** DEBUG step=1 : T [vterm.icc: 40] *** DEBUG step=10 : T [vterm.icc: 40] *** DEBUG step=100 : ... ** Automated testing By their very nature, graphical terminal emulators are interactive programs. Therefore, testing them in an automated fashion (e.g., for regression testing) can be tricky. We employ a method suitable as a general means to automation, as it is independent of the terminal under test: it does not require modifying the program by e.g., implementing test hooks to inject events or report screen content. This allows us to include several established terminal emulators in the test along with Zutty to research the state of the art and see how different programs stack up against each other. Prerequisites: - imagemagick (for convert & identify), wmctrl, xvkbd : apt-get install imagemagick wmctrl xvkbd On a high level, testing consists of these steps: - Start the terminal (the unit under test) as a subprocess and note its pid - Obtain its X window id: : wmctrl -lp | grep <pid> | awk '{print $1}' - use =xvkbd= to send events to the window: : xvkbd -window <id> -no-jump-pointer -text "\D3\{+1}\D3\{-1}\D3\{+Return}\D3\{-Return}\D3" Note the explicit keysym presses and releases, plus the interleaved delays. For reference, see: http://t-sato.in.coocan.jp/xvkbd/ - Make a screenshot of the window via the window id : xwd -nobdrs -id <id> | convert xwd:- png:- > <shot-name>.png - Generate a signature of the screen content, to be compared against a reference value: : identify -verbose <shot-name>.png | grep signature | awk '{print $2}' | cut -32 (We cut the hash in half to make it less unwieldy.) Having consistent hashes that depend only on the rendered pixel image is convenient, as we do not need to store the reference images themselves beside our test script. The above steps are automated by some fairly straightforward bash scripts under the =test/= subdirectory. These scripts all source the =testbase.sh= script, which constitutes the test library. *** CAVEATs While Zutty itself compiles and runs on Linux as well as BSD platforms, the test toolkit assumes a specific development environment (most notably: Linux platform with =bash= as the default shell, and the presence of a window manager with certain standard capabilities). Please also note that the reference signatures of screen captures are valid for ImageMagick version 6 only; version 7 has altered the image signature hash algorithm, which breaks the hashes included in the tests. *** Anatomy of a test script Each executable script under =test/= is an individually runnable test suite. It is written as a plain old bash script, sourcing the =testbase.sh= test code library and using its facilities. For example, =truecolor.sh= is a very simple test script to test support for setting color attributes to truecolor (24 bit) values. The full script is reproduced below: #+BEGIN_SRC sh #!/usr/bin/env bash cd $(dirname $0) source testbase.sh IN "source truecolor_inc.sh\r" SNAP truecolor_01 33a31e4d3b9fbe486c27b01764dc1823 #+END_SRC The script starts by declaring itself as a shell script, then setting up its working directory to be the location of the script (a convenience to make relative file paths work in later parts of the script, independent of the location the script was invoked from). Then =testbase.sh= is sourced. The actual test code is just two lines, starting with the commands =IN= and =SNAP=. These are invocations of functions defined in =testbase.sh= and execute in the environment set up by sourcing that file. =IN= will send the specified string, as keyboard input, to the terminal under test. In our example, the shell running in the terminal will source the file =truecolor_inc.sh= that contains some setup code (not reproduced here) to make a certain pattern appear on the terminal. Note the trailing =\r= that will result in a virtual =Enter= keypress. The subsequent =SNAP= will capture the resulting terminal window content under the name =truecolor_01=, generate a signature (hash) of it, and compare that with the supplied value. If there is a match, the output is verified to be correct; else a test failure is reported. The snap name is used to save the captured window image under =test/output/<profile>=. This is useful for later inspection of test results. With the default profile (see [[Test profiles]] below), the output of this test will be saved as =test/output/zutty/truecolor_01.png=. There are some other useful functions exported by the test framework, e.g.: =CHECK_DEPS=, =CHECK_FILES=, and =WAIT_FOR_DOT_COMPLETE=. If you encounter them in test script code, it is best to look directly in =testbase.sh= for their implementation. Note that starting and stopping of the terminal under test is done as part of the test framework and nothing is explicitly written in the test script. See [[Test profiles]] below on how to control the details of this process. *** Common test script options Test scripts similar to the one shown above (building on =testbase.sh=) all take a uniform set of command line options. All arguments are optional, below defaults are in effect for omitted ones. Syntax: =--<arg-name>=<arg-value>=; value defaults to "yes" if omitted. : Option Default : -------------------------- : --ci-mode no : --debug no : --profile zutty : --step no : --update-sig no **** =--step= The =--step= option can be given without argument, in which case it will be equivalent to =--step=yes=, or given as =--step=new=. The former one will result in step mode, which will pause immediately after each snapshot is taken (the terminal under test still displaying this output, allowing visual inspection), and display a prompt: : [S]tep / [N]ew only / [C]ontinue / [Q]uit (s/n/c/q) ? This allows the user to choose how to proceed: - *S* - continue stepping, i.e., stop after each snapshot - *N* - continue without stopping, except on new snapshots - *C* - continue without ever stopping again - *Q* - quit the test The =new= option is useful when developing a test suite. It will run the test script forward until a =SNAP= command without a verification hash is found. **** =--update-sig= Enabling =--update-sig= will result in a prompt on a verification failure, i.e., when the =SNAP= command captures a screenshot with a different hash than the reference stored in the script: : Update signature: [y]es / [N]o / [a]ll ? By answering *Y* here, the signature in the script will be updated. By answering *A*, all future differences will also be updated without a further prompt. This is useful in case the behaviour of Zutty is changed in a way that alters its output; in case it is established that the new output is "more correct" than the previous one; and we want to adapt the tests to verify against this new output in the future. Use this with a great deal of caution. It is recommended to use *Y* in favour of *A* and before answering each prompt, to do a careful visual inspection of each screen for correctness. **** =--profile= See [[Test profiles]] below. **** =--ci-mode= The =--ci-mode= option sets up the test script to execute in an unattended manner, suitable for automated testing. Step mode is turned off (overriding =--step=), signature updates are turned off (overriding =--update-sig=), and the script is set up to immediately exit with a nonzero code on a verification failure. **** =--debug= The =--debug= option sets the variable =DEBUG= to =yes= (instead of its default value =no=). This is used by the =zutty= test profile to ensure that the correct UUT executable (=zutty= vs. =zutty.dbg=) is launched. This option is currently not used by any other profile, nor for any other, more general purpose. *** Correctness tests The list of correctness tests (automatically run in sequence by [[The CI test script]]): - =keys.sh=: Keyboard input handling (see [[./KEYS.org][Key mapping sequences]] for further documentation). Note: This test might fail if your computer is configured to use a non-US keyboard. - =nonascii.sh=: Test displaying non-ascii (double-width and other exotic) characters, based on the Emacs 'hello' file (=M-x view-hello-file=) - =scrollback.sh=: Scrollback (page history) support - =title.sh=: Setting the window title from within the terminal via escape sequences - =truecolor.sh=: True color support - =utf8.sh=: UTF-8 support (based on the [[../test/UTF-8-test.txt][UTF-8 decoder capability and stress test]] by Markus Kuhn) - =vttest.sh=: VTTEST screens. Note: This suite depends on a specific version of =vttest=, and will complain if the version found does not match. Just run the vttest install script mentioned by the error message, and you should be good to go. - =wraptest.sh=: Additional tests for verifying the correctness of last-column/pending-wrap subtleties. Note: This suite depends on the [[https://github.com/mattiase/wraptest][wraptest]] program (not bundled with the zutty source repository). Just run the install script mentioned by the error message you get on the first run, and you should be good to go. Apart from running all the tests via [[The CI test script]] (which you should routinely run during development, and especially before sending a patch or opening a pull request), it is also possible to run any of the above tests manually. For example, to run the test automating a traversal of Vttest's menu system (and in case of running against Zutty, also verifying that results are as expected): : test/vttest.sh Do not forget about the [[Common test script options]] above; those become useful during development (both of Zutty itself and the test suites). For example, to run the above test step-by-step (stopping at each image checkpoint): : test/vttest.sh --step *** Font rasterization tests A separate test =fonts.sh= exists that is very similar to the correctness tests above, but is not part of [[The CI test script]]. This renders a captured hardcopy of a terminal-intensive program (BpyTOP) exercising many features (chiefly, Unicode line drawing glyphs) that are helpful in spotting errors in the font loading and rasterization process employed by Zutty, and validates the result across a matrix of several font faces and sizes. Please read the commentary in the test script for the fonts required to run the test. *** Performance tests The performance of Zutty can be verified with the below tests. These are to be run manually (similar to how you run any of the [[Correctness tests]] on an ad-hoc basis). The goal of these tests is to get a handle on performance under repeatable circumstances. Since everyone's hardware and systems are different, it does not make too much sense to compare numbers obtained by different people at different times. Rather, it is most useful to compare the results obtained on the same system (running the comparison tests after one another without any changes to the rest of the system), with the goal of comparing a proposed set of patches to the baseline of Zutty, or to compare Zutty with another terminal emulator. Needless to say: when running the below scripts, your machine should be otherwise idle. While correctness tests should ideally be run on both the debug build (do this by passing =--debug= to the test scripts) and the normal build, it does not make sense to run performance tests on the debug build. The results would be meaningless due to excessive logging and hitting other debug-only code paths. Current performance tests: - =cat_dict.sh=: Arguably the dumbest possible performance test of any text terminal, this test consists of outputting a very long text file containing (mostly) very short lines with English words, one per line. This test will be repeated a number of times (check =TIMES= in the script) and the overall timing and data throughput will be computed at the end. Since the input does not contain any terminal controls (escape sequences), it is a measure of the raw incoming data rate the terminal can sustain while frequently forced to scroll/page its output. This load resembles one extreme end of the way a terminal can be used. - =cat_vtscript.sh=: This test generates load resembling the other extreme end of possible terminal usage, by outputting the stream of data written to the terminal in the course of the VTTEST cycle (see =vttest.sh= among the [[Correctness tests]]). However, instead of verifying the correctness of the generated screen output, here we are interested in the performance of processing the input stream heavy on all kinds of escape sequences. Screen updates are dominated by intra-screen rewrites and relatively little scrolling/paging activity is forced. Similar to the =cat_dict.sh= test, the input is fed into the terminal a number of times, and overall timing and throughput is measured and calculated. *** The CI test script The script =test/run_ci.sh= will run all automated [[Correctness tests]] in sequence with the =--ci-mode= option, stopping and exiting with a nonzero return code if any of them exits with an error, and concluding with a confirmatory message and zero return code otherwise. You should always run this at a convenient time (it will occupy your screen for about 25 minutes) and observe the successful result before sending a patch with your changes. For extra confidence, build Zutty both with and without debug, and run the CI script both with and without the =--debug= option. If you have changed anything related to font loading or rasterization (=font.cc= or =fontpack.cc=), make sure you run the [[Font rasterization tests]] as well. Please read the commentary in the test script for more context. *** Test profiles Given that our method of testing is independent of the terminal itself (meaning that we do not rely on any hooks or test instrumentation in the terminal itself), we can run the same tests against other terminals, too. This is useful for comparison and research purposes. The default profile is =zutty=, invoking the locally built Zutty executable as the unit under test. Profiles are defined as shell include files under =test/profiles=, and can be invoked by passing the =--profile= option to the test script. For example, to run the VTTEST suite against =xterm=: : ./test/vttest.sh [--step] --profile=xterm Available profiles can be enumerated by looking in =test/profiles/= (pass their name without the =.sh= extension to =--profile=, as above). The list is also printed by the test script in case an invalid profile name is given. Each test profile contains a program invocation assigned to =UUT_EXE=, used to launch the terminal under test with the right arguments. The terminal must be configured so that its geometry is 80 characters wide and 24 rows tall. Some other variables set up in the profile encode different capabilities to control the test scripts so that unsupported features are skipped (and the terminal does not encounter confusing escape sequences). Examples: =MISSING_ANSWERBACK=, =MISSING_DSR=, =MISSING_SECONDARY_DA=, =SUPPORTS_VT52=, =SUPPORTS_VT220=. Please check the script files for details. Note that validation against the stored image hashes does not make sense, unless preparations are made to ensure that the terminal settings (size, font, etc.) are perfectly identical. Even then, differences from the valid output stored for Zutty do not necessarily constitute bugs in any other terminal, unless the rendered screen content is visibly wrong. For this reason, auto-validation of the saved screens runs in a relaxed mode for all profiles other than =zutty=. This means that for terminals other than Zutty, matches are still prominently displayed (by a line reading =MATCH= in green), while non-matches are considered normal and indicated by =DONE=, followed by a metric of the image difference compared to the reference. The output image and the image generated for Zutty will be diffed, resulting in a difference image that will highlight differences with red. ** Contribution guide There is no commercial entity behind Zutty. It is a volunteer effort with extremely limited resources. Your contributions will be duly considered, but please discuss your plans with us before starting out. Otherwise, you run the risk of being ignored after having put time and effort into the preparation of a patch. Not every aspect of what makes a good contribution can be codified (we believe having good taste plays an important role), but as a minimum, please keep the following in mind: *** Respect existing coding style