1{ system ? builtins.currentSystem,
2 config ? {},
3 pkgs ? import ../.. { inherit system config; }
4}:
5
6with import ../lib/testing-python.nix { inherit system pkgs; };
7
8let
9 readyFile = "/tmp/readerReady";
10 resultFile = "/tmp/readerResult";
11
12 testReader = pkgs.writeScript "test-input-reader" ''
13 rm -f ${resultFile} ${resultFile}.tmp
14 logger "testReader: START: Waiting for $1 characters, expecting '$2'."
15 touch ${readyFile}
16 read -r -N $1 chars
17 rm -f ${readyFile}
18
19 if [ "$chars" == "$2" ]; then
20 logger -s "testReader: PASS: Got '$2' as expected." 2>${resultFile}.tmp
21 else
22 logger -s "testReader: FAIL: Expected '$2' but got '$chars'." 2>${resultFile}.tmp
23 fi
24 # rename after the file is written to prevent a race condition
25 mv ${resultFile}.tmp ${resultFile}
26 '';
27
28
29 mkKeyboardTest = layout: { extraConfig ? {}, tests }: with pkgs.lib; makeTest {
30 name = "keymap-${layout}";
31
32 machine.console.keyMap = mkOverride 900 layout;
33 machine.services.xserver.desktopManager.xterm.enable = false;
34 machine.services.xserver.layout = mkOverride 900 layout;
35 machine.imports = [ ./common/x11.nix extraConfig ];
36
37 testScript = ''
38 import json
39 import shlex
40
41
42 def run_test_case(cmd, xorg_keymap, test_case_name, inputs, expected):
43 with subtest(test_case_name):
44 assert len(inputs) == len(expected)
45 machine.execute("rm -f ${readyFile} ${resultFile}")
46
47 # set up process that expects all the keys to be entered
48 machine.succeed(
49 "{} {} {} {} >&2 &".format(
50 cmd,
51 "${testReader}",
52 len(inputs),
53 shlex.quote("".join(expected)),
54 )
55 )
56
57 if xorg_keymap:
58 # make sure the xterm window is open and has focus
59 machine.wait_for_window("testterm")
60 machine.wait_until_succeeds(
61 "${pkgs.xdotool}/bin/xdotool search --sync --onlyvisible "
62 "--class testterm windowfocus --sync"
63 )
64
65 # wait for reader to be ready
66 machine.wait_for_file("${readyFile}")
67
68 # send all keys
69 for key in inputs:
70 machine.send_key(key)
71
72 # wait for result and check
73 machine.wait_for_file("${resultFile}")
74 machine.succeed("grep -q 'PASS:' ${resultFile}")
75
76
77 with open("${pkgs.writeText "tests.json" (builtins.toJSON tests)}") as json_file:
78 tests = json.load(json_file)
79
80 # These environments used to run in the opposite order, causing the
81 # following error at openvt startup.
82 #
83 # openvt: Couldn't deallocate console 1
84 #
85 # This error did not appear in successful runs.
86 # I don't know the exact cause, but I it seems that openvt and X are
87 # fighting over the virtual terminal. This does not appear to be a problem
88 # when the X test runs first.
89 keymap_environments = {
90 "Xorg Keymap": "DISPLAY=:0 xterm -title testterm -class testterm -fullscreen -e",
91 "VT Keymap": "openvt -sw --",
92 }
93
94 machine.wait_for_x()
95
96 for keymap_env_name, command in keymap_environments.items():
97 with subtest(keymap_env_name):
98 for test_case_name, test_data in tests.items():
99 run_test_case(
100 command,
101 False,
102 test_case_name,
103 test_data["qwerty"],
104 test_data["expect"],
105 )
106 '';
107 };
108
109in pkgs.lib.mapAttrs mkKeyboardTest {
110 azerty = {
111 tests = {
112 azqw.qwerty = [ "q" "w" ];
113 azqw.expect = [ "a" "z" ];
114 altgr.qwerty = [ "alt_r-2" "alt_r-3" "alt_r-4" "alt_r-5" "alt_r-6" ];
115 altgr.expect = [ "~" "#" "{" "[" "|" ];
116 };
117
118 extraConfig.console.keyMap = "fr";
119 extraConfig.services.xserver.layout = "fr";
120 };
121
122 bone = {
123 tests = {
124 layer1.qwerty = [ "f" "j" ];
125 layer1.expect = [ "e" "n" ];
126 layer2.qwerty = [ "shift-f" "shift-j" "shift-6" ];
127 layer2.expect = [ "E" "N" "$" ];
128 layer3.qwerty = [ "caps_lock-d" "caps_lock-f" ];
129 layer3.expect = [ "{" "}" ];
130 };
131
132 extraConfig.console.keyMap = "bone";
133 extraConfig.services.xserver.layout = "de";
134 extraConfig.services.xserver.xkbVariant = "bone";
135 };
136
137 colemak = {
138 tests = {
139 homerow.qwerty = [ "a" "s" "d" "f" "j" "k" "l" "semicolon" ];
140 homerow.expect = [ "a" "r" "s" "t" "n" "e" "i" "o" ];
141 };
142
143 extraConfig.console.keyMap = "colemak";
144 extraConfig.services.xserver.layout = "us";
145 extraConfig.services.xserver.xkbVariant = "colemak";
146 };
147
148 dvorak = {
149 tests = {
150 homerow.qwerty = [ "a" "s" "d" "f" "j" "k" "l" "semicolon" ];
151 homerow.expect = [ "a" "o" "e" "u" "h" "t" "n" "s" ];
152 symbols.qwerty = [ "q" "w" "e" "minus" "equal" ];
153 symbols.expect = [ "'" "," "." "[" "]" ];
154 };
155
156 extraConfig.console.keyMap = "dvorak";
157 extraConfig.services.xserver.layout = "us";
158 extraConfig.services.xserver.xkbVariant = "dvorak";
159 };
160
161 dvorak-programmer = {
162 tests = {
163 homerow.qwerty = [ "a" "s" "d" "f" "j" "k" "l" "semicolon" ];
164 homerow.expect = [ "a" "o" "e" "u" "h" "t" "n" "s" ];
165 numbers.qwerty = map (x: "shift-${x}")
166 [ "1" "2" "3" "4" "5" "6" "7" "8" "9" "0" "minus" ];
167 numbers.expect = [ "%" "7" "5" "3" "1" "9" "0" "2" "4" "6" "8" ];
168 symbols.qwerty = [ "1" "2" "3" "4" "5" "6" "7" "8" "9" "0" "minus" ];
169 symbols.expect = [ "&" "[" "{" "}" "(" "=" "*" ")" "+" "]" "!" ];
170 };
171
172 extraConfig.console.keyMap = "dvorak-programmer";
173 extraConfig.services.xserver.layout = "us";
174 extraConfig.services.xserver.xkbVariant = "dvp";
175 };
176
177 neo = {
178 tests = {
179 layer1.qwerty = [ "f" "j" ];
180 layer1.expect = [ "e" "n" ];
181 layer2.qwerty = [ "shift-f" "shift-j" "shift-6" ];
182 layer2.expect = [ "E" "N" "$" ];
183 layer3.qwerty = [ "caps_lock-d" "caps_lock-f" ];
184 layer3.expect = [ "{" "}" ];
185 };
186
187 extraConfig.console.keyMap = "neo";
188 extraConfig.services.xserver.layout = "de";
189 extraConfig.services.xserver.xkbVariant = "neo";
190 };
191
192 qwertz = {
193 tests = {
194 zy.qwerty = [ "z" "y" ];
195 zy.expect = [ "y" "z" ];
196 altgr.qwerty = map (x: "alt_r-${x}")
197 [ "q" "less" "7" "8" "9" "0" ];
198 altgr.expect = [ "@" "|" "{" "[" "]" "}" ];
199 };
200
201 extraConfig.console.keyMap = "de";
202 extraConfig.services.xserver.layout = "de";
203 };
204}