summary refs log tree commit diff
path: root/nixos/tests/terminal-emulators.nix
blob: 60161b80b96519e6e0f97d0bd1c5671077d18e19 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# Terminal emulators all present a pretty similar interface.
# That gives us an opportunity to easily test their basic functionality with a single codebase.
#
# There are two tests run on each terminal emulator
# - can it successfully execute a command passed on the cmdline?
# - can it successfully display a colour?
# the latter is used as a proxy for "can it display text?", without going through all the intricacies of OCR.
#
# 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).
#
# Future work:
# - Wayland support (both for testing the existing terminals, and for testing wayland-only terminals like foot and havoc)
# - Test keyboard input? (skipped for now, to eliminate the possibility of race conditions and focus issues)

{ system ? builtins.currentSystem,
  config ? {},
  pkgs ? import ../.. { inherit system config; }
}:

with import ../lib/testing-python.nix { inherit system pkgs; };
with pkgs.lib;

let tests = {
      alacritty.pkg = p: p.alacritty;

      contour.pkg = p: p.contour;
      contour.cmd = "contour $command";

      cool-retro-term.pkg = p: p.cool-retro-term;
      cool-retro-term.colourTest = false; # broken by gloss effect

      ctx.pkg = p: p.ctx;
      ctx.pinkValue = "#FE0065";

      darktile.pkg = p: p.darktile;

      eterm.pkg = p: p.eterm;
      eterm.executable = "Eterm";
      eterm.pinkValue = "#D40055";

      germinal.pkg = p: p.germinal;

      gnome-terminal.pkg = p: p.gnome.gnome-terminal;

      guake.pkg = p: p.guake;
      guake.cmd = "SHELL=$command guake --show";
      guake.kill = true;

      hyper.pkg = p: p.hyper;

      kermit.pkg = p: p.kermit-terminal;

      kgx.pkg = p: p.kgx;
      kgx.cmd = "kgx -e $command";
      kgx.kill = true;

      kitty.pkg = p: p.kitty;
      kitty.cmd = "kitty $command";

      konsole.pkg = p: p.plasma5Packages.konsole;

      lxterminal.pkg = p: p.lxterminal;

      mate-terminal.pkg = p: p.mate.mate-terminal;
      mate-terminal.cmd = "SHELL=$command mate-terminal --disable-factory"; # factory mode uses dbus, and we don't have a proper dbus session set up

      mlterm.pkg = p: p.mlterm;

      mrxvt.pkg = p: p.mrxvt;

      qterminal.pkg = p: p.lxqt.qterminal;
      qterminal.kill = true;

      roxterm.pkg = p: p.roxterm;
      roxterm.cmd = "roxterm -e $command";

      sakura.pkg = p: p.sakura;

      st.pkg = p: p.st;
      st.kill = true;

      stupidterm.pkg = p: p.stupidterm;
      stupidterm.cmd = "stupidterm -- $command";

      terminator.pkg = p: p.terminator;
      terminator.cmd = "terminator -e $command";

      terminology.pkg = p: p.enlightenment.terminology;
      terminology.cmd = "SHELL=$command terminology --no-wizard=true";
      terminology.colourTest = false; # broken by gloss effect

      termite.pkg = p: p.termite;

      termonad.pkg = p: p.termonad;

      tilda.pkg = p: p.tilda;

      tilix.pkg = p: p.tilix;
      tilix.cmd = "tilix -e $command";

      urxvt.pkg = p: p.rxvt-unicode;

      wayst.pkg = p: p.wayst;
      wayst.pinkValue = "#FF0066";

      wezterm.pkg = p: p.wezterm;

      xfce4-terminal.pkg = p: p.xfce.xfce4-terminal;

      xterm.pkg = p: p.xterm;
    };
in mapAttrs (name: { pkg, executable ? name, cmd ? "SHELL=$command ${executable}", colourTest ? true, pinkValue ? "#FF0087", kill ? false }: makeTest
{
  name = "terminal-emulator-${name}";
  meta = with pkgs.stdenv.lib.maintainers; {
    maintainers = [ jjjollyjim ];
  };

  machine = { pkgsInner, ... }:

  {
    imports = [ ./common/x11.nix ./common/user-account.nix ];

    # Hyper (and any other electron-based terminals) won't run as root
    test-support.displayManager.auto.user = "alice";

    environment.systemPackages = [
      (pkg pkgs)
      (pkgs.writeShellScriptBin "report-success" ''
        echo 1 > /tmp/term-ran-successfully
        ${optionalString kill "pkill ${executable}"}
      '')
      (pkgs.writeShellScriptBin "display-colour" ''
        # A 256-colour background colour code for pink, then spaces.
        #
        # Background is used rather than foreground to minimize the effect of anti-aliasing.
        #
        # Keep adding more in case the window is partially offscreen to the left or requires
        # a change to correctly redraw after initialising the window (as with ctx).

        while :
        do
            echo -ne "\e[48;5;198m                   "
            sleep 0.5
        done
        sleep infinity
      '')
      (pkgs.writeShellScriptBin "run-in-this-term" "sudo -u alice run-in-this-term-wrapped $1")

      (pkgs.writeShellScriptBin "run-in-this-term-wrapped" "command=\"$(which \"$1\")\"; ${cmd}")
    ];

    # Helpful reminder to add this test to passthru.tests
    warnings = if !((pkg pkgs) ? "passthru" && (pkg pkgs).passthru ? "tests") then [ "The package for ${name} doesn't have a passthru.tests" ] else [ ];
  };

  # We need imagemagick, though not tesseract
  enableOCR = true;

  testScript = { nodes, ... }: let
  in ''
    with subtest("wait for x"):
        start_all()
        machine.wait_for_x()

    with subtest("have the terminal run a command"):
        # We run this command synchronously, so we can be certain the exit codes are happy
        machine.${if kill then "execute" else "succeed"}("run-in-this-term report-success")
        machine.wait_for_file("/tmp/term-ran-successfully")
    ${optionalString colourTest ''

    import tempfile
    import subprocess


    def check_for_pink(final=False) -> bool:
        with tempfile.NamedTemporaryFile() as tmpin:
            machine.send_monitor_command("screendump {}".format(tmpin.name))

            cmd = 'convert {} -define histogram:unique-colors=true -format "%c" histogram:info:'.format(
                tmpin.name
            )
            ret = subprocess.run(cmd, shell=True, capture_output=True)
            if ret.returncode != 0:
                raise Exception(
                    "image analysis failed with exit code {}".format(ret.returncode)
                )

            text = ret.stdout.decode("utf-8")
            return "${pinkValue}" in text


    with subtest("ensuring no pink is present without the terminal"):
        assert (
            check_for_pink() == False
        ), "Pink was present on the screen before we even launched a terminal!"

    with subtest("have the terminal display a colour"):
        # We run this command in the background
        machine.shell.send(b"(run-in-this-term display-colour |& systemd-cat -t terminal) &\n")

        with machine.nested("Waiting for the screen to have pink on it:"):
            retry(check_for_pink)
  ''}'';
}

  ) tests