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
208
209
210
211
212
213
214
215
216
|
# 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;
# times out after spending many hours
#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;
deepin-terminal.pkg = p: p.deepin.deepin-terminal;
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;
rio.pkg = p: p.rio;
rio.cmd = "rio -e $command";
rio.colourTest = false; # the rendering is changing too much so colors change every release.
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";
# times out after spending many hours
#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.lib.maintainers; {
maintainers = [ jjjollyjim ];
};
nodes.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
assert machine.shell is not None
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
|