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