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