at master 7.3 kB view raw
1# Terminal emulators all present a pretty similar interface. 2# That gives us an opportunity to easily test their basic functionality with a single codebase. 3# 4# There are two tests run on each terminal emulator 5# - can it successfully execute a command passed on the cmdline? 6# - can it successfully display a colour? 7# the latter is used as a proxy for "can it display text?", without going through all the intricacies of OCR. 8# 9# 256-colour terminal mode is used to display the test colour, since it has a universally-applicable palette (unlike 8- and 16- colour, where the colours are implementation-defined), and it is widely supported (unlike 24-bit colour). 10# 11# Future work: 12# - Wayland support (both for testing the existing terminals, and for testing wayland-only terminals like foot and havoc) 13# - Test keyboard input? (skipped for now, to eliminate the possibility of race conditions and focus issues) 14 15{ 16 system ? builtins.currentSystem, 17 config ? { }, 18 pkgs ? import ../.. { inherit system config; }, 19}: 20 21with import ../lib/testing-python.nix { inherit system pkgs; }; 22with pkgs.lib; 23 24let 25 tests = { 26 alacritty.pkg = p: p.alacritty; 27 28 contour.pkg = p: p.contour; 29 contour.cmd = "contour early-exit-threshold 0 execute $command"; 30 31 cool-retro-term.pkg = p: p.cool-retro-term; 32 cool-retro-term.colourTest = false; # broken by gloss effect 33 34 ctx.pkg = p: p.ctx; 35 ctx.pinkValue = "#FE0065"; 36 37 darktile.pkg = p: p.darktile; 38 39 germinal.pkg = p: p.germinal; 40 41 ghostty.pkg = p: p.ghostty; 42 43 gnome-terminal.pkg = p: p.gnome-terminal; 44 45 guake.pkg = p: p.guake; 46 guake.cmd = "SHELL=$command guake --show"; 47 guake.kill = true; 48 49 hyper.pkg = p: p.hyper; 50 51 kermit.pkg = p: p.kermit-terminal; 52 53 kgx.pkg = p: p.kgx; 54 kgx.cmd = "kgx -e $command"; 55 kgx.kill = true; 56 57 kitty.pkg = p: p.kitty; 58 kitty.cmd = "kitty $command"; 59 60 konsole.pkg = p: p.kdePackages.konsole; 61 62 lxterminal.pkg = p: p.lxterminal; 63 64 mate-terminal.pkg = p: p.mate.mate-terminal; 65 mate-terminal.cmd = "SHELL=$command mate-terminal --disable-factory"; # factory mode uses dbus, and we don't have a proper dbus session set up 66 67 mlterm.pkg = p: p.mlterm; 68 69 qterminal.pkg = p: p.lxqt.qterminal; 70 qterminal.kill = true; 71 72 rio.pkg = p: p.rio; 73 rio.cmd = "rio -e $command"; 74 rio.colourTest = false; # the rendering is changing too much so colors change every release. 75 76 roxterm.pkg = p: p.roxterm; 77 roxterm.cmd = "roxterm -e $command"; 78 79 sakura.pkg = p: p.sakura; 80 81 st.pkg = p: p.st; 82 st.kill = true; 83 84 stupidterm.pkg = p: p.stupidterm; 85 stupidterm.cmd = "stupidterm -- $command"; 86 87 terminator.pkg = p: p.terminator; 88 terminator.cmd = "terminator -e $command"; 89 90 terminology.pkg = p: p.enlightenment.terminology; 91 terminology.cmd = "SHELL=$command terminology --no-wizard=true"; 92 terminology.colourTest = false; # broken by gloss effect 93 94 termite.pkg = p: p.termite; 95 96 termonad.pkg = p: p.termonad; 97 98 tilda.pkg = p: p.tilda; 99 100 tilix.pkg = p: p.tilix; 101 tilix.cmd = "tilix -e $command"; 102 103 urxvt.pkg = p: p.rxvt-unicode; 104 105 wayst.pkg = p: p.wayst; 106 wayst.pinkValue = "#FF0066"; 107 108 # times out after spending many hours 109 #wezterm.pkg = p: p.wezterm; 110 111 xfce4-terminal.pkg = p: p.xfce.xfce4-terminal; 112 113 xterm.pkg = p: p.xterm; 114 115 zutty.pkg = p: p.zutty; 116 }; 117in 118mapAttrs ( 119 name: 120 { 121 pkg, 122 executable ? name, 123 cmd ? "SHELL=$command ${executable}", 124 colourTest ? true, 125 pinkValue ? "#FF0087", 126 kill ? false, 127 }: 128 makeTest { 129 name = "terminal-emulator-${name}"; 130 meta = with pkgs.lib.maintainers; { 131 maintainers = [ jjjollyjim ]; 132 }; 133 134 nodes.machine = 135 { pkgsInner, ... }: 136 137 { 138 imports = [ 139 ./common/x11.nix 140 ./common/user-account.nix 141 ]; 142 143 # Hyper (and any other electron-based terminals) won't run as root 144 test-support.displayManager.auto.user = "alice"; 145 146 environment.systemPackages = [ 147 (pkg pkgs) 148 (pkgs.writeShellScriptBin "report-success" '' 149 echo 1 > /tmp/term-ran-successfully 150 ${optionalString kill "pkill ${executable}"} 151 '') 152 (pkgs.writeShellScriptBin "display-colour" '' 153 # A 256-colour background colour code for pink, then spaces. 154 # 155 # Background is used rather than foreground to minimize the effect of anti-aliasing. 156 # 157 # Keep adding more in case the window is partially offscreen to the left or requires 158 # a change to correctly redraw after initialising the window (as with ctx). 159 160 while : 161 do 162 echo -ne "\e[48;5;198m " 163 sleep 0.5 164 done 165 sleep infinity 166 '') 167 (pkgs.writeShellScriptBin "run-in-this-term" "sudo -u alice run-in-this-term-wrapped $1") 168 169 (pkgs.writeShellScriptBin "run-in-this-term-wrapped" "command=\"$(which \"$1\")\"; ${cmd}") 170 ]; 171 172 # Helpful reminder to add this test to passthru.tests 173 warnings = 174 if !((pkg pkgs) ? "passthru" && (pkg pkgs).passthru ? "tests") then 175 [ "The package for ${name} doesn't have a passthru.tests" ] 176 else 177 [ ]; 178 }; 179 180 # We need imagemagick, though not tesseract 181 enableOCR = true; 182 183 testScript = 184 { nodes, ... }: 185 let 186 in 187 '' 188 with subtest("wait for x"): 189 start_all() 190 machine.wait_for_x() 191 192 with subtest("have the terminal run a command"): 193 # We run this command synchronously, so we can be certain the exit codes are happy 194 machine.${if kill then "execute" else "succeed"}("run-in-this-term report-success") 195 machine.wait_for_file("/tmp/term-ran-successfully") 196 ${optionalString colourTest '' 197 198 import tempfile 199 import subprocess 200 201 202 def check_for_pink(final=False) -> bool: 203 with tempfile.NamedTemporaryFile() as tmpin: 204 machine.send_monitor_command("screendump {}".format(tmpin.name)) 205 206 cmd = 'convert {} -define histogram:unique-colors=true -format "%c" histogram:info:'.format( 207 tmpin.name 208 ) 209 ret = subprocess.run(cmd, shell=True, capture_output=True) 210 if ret.returncode != 0: 211 raise Exception( 212 "image analysis failed with exit code {}".format(ret.returncode) 213 ) 214 215 text = ret.stdout.decode("utf-8") 216 return "${pinkValue}" in text 217 218 219 with subtest("ensuring no pink is present without the terminal"): 220 assert ( 221 check_for_pink() == False 222 ), "Pink was present on the screen before we even launched a terminal!" 223 224 with subtest("have the terminal display a colour"): 225 # We run this command in the background 226 assert machine.shell is not None 227 machine.shell.send(b"(run-in-this-term display-colour |& systemd-cat -t terminal) &\n") 228 229 with machine.nested("Waiting for the screen to have pink on it:"): 230 retry(check_for_pink) 231 ''}''; 232 } 233 234) tests