1{
2 lib,
3 pkgs,
4 ...
5}:
6let
7 user = "alice";
8
9 client =
10 { pkgs, ... }:
11
12 {
13 imports = [
14 ./common/user-account.nix
15 ./common/x11.nix
16 ];
17 hardware.graphics.enable = true;
18 virtualisation.memorySize = 384;
19 environment = {
20 systemPackages = [ pkgs.armagetronad ];
21 variables.XAUTHORITY = "/home/${user}/.Xauthority";
22 };
23 test-support.displayManager.auto.user = user;
24 };
25
26in
27{
28 name = "armagetronad";
29 meta = with lib.maintainers; {
30 maintainers = [ numinit ];
31 };
32
33 enableOCR = true;
34
35 nodes = {
36 server = {
37 services.armagetronad.servers = {
38 high-rubber = {
39 enable = true;
40 name = "Smoke Test High Rubber Server";
41 port = 4534;
42 settings = {
43 SERVER_OPTIONS = "High Rubber server made to run smoke tests.";
44 CYCLE_RUBBER = 40;
45 SIZE_FACTOR = 0.5;
46 };
47 roundSettings = {
48 SAY = [
49 "NixOS Smoke Test Server"
50 "https://nixos.org"
51 ];
52 };
53 };
54 sty = {
55 enable = true;
56 name = "Smoke Test sty+ct+ap Server";
57 package = pkgs.armagetronad."0.2.9-sty+ct+ap".dedicated;
58 port = 4535;
59 settings = {
60 SERVER_OPTIONS = "sty+ct+ap server made to run smoke tests.";
61 CYCLE_RUBBER = 20;
62 SIZE_FACTOR = 0.5;
63 };
64 roundSettings = {
65 SAY = [
66 "NixOS Smoke Test sty+ct+ap Server"
67 "https://nixos.org"
68 ];
69 };
70 };
71 trunk = {
72 enable = true;
73 name = "Smoke Test trunk Server";
74 package = pkgs.armagetronad."0.4".dedicated;
75 port = 4536;
76 settings = {
77 SERVER_OPTIONS = "0.4 server made to run smoke tests.";
78 CYCLE_RUBBER = 20;
79 SIZE_FACTOR = 0.5;
80 };
81 roundSettings = {
82 SAY = [
83 "NixOS Smoke Test 0.4 Server"
84 "https://nixos.org"
85 ];
86 };
87 };
88 };
89 };
90
91 client1 = client;
92 client2 = client;
93 };
94
95 testScript =
96 let
97 xdo =
98 name: text:
99 let
100 xdoScript = pkgs.writeText "${name}.xdo" text;
101 in
102 "${pkgs.xdotool}/bin/xdotool ${xdoScript}";
103 in
104 ''
105 import shlex
106 import threading
107 from collections import namedtuple
108
109 class Client(namedtuple('Client', ('node', 'name'))):
110 def send(self, *keys):
111 for key in keys:
112 self.node.send_key(key)
113
114 def send_on(self, text, *keys):
115 self.node.wait_for_text(text)
116 self.send(*keys)
117
118 Server = namedtuple('Server', ('node', 'name', 'address', 'port', 'welcome', 'player1', 'player2'))
119
120 # Clients and their in-game names
121 clients = (
122 Client(client1, 'Arduino'),
123 Client(client2, 'SmOoThIcE')
124 )
125
126 # Server configs.
127 servers = (
128 Server(server, 'high-rubber', 'server', 4534, 'NixOS Smoke Test Server', 'SmOoThIcE', 'Arduino'),
129 Server(server, 'sty', 'server', 4535, 'NixOS Smoke Test sty+ct+ap Server', 'Arduino', 'SmOoThIcE'),
130 Server(server, 'trunk', 'server', 4536, 'NixOS Smoke Test 0.4 Server', 'Arduino', 'SmOoThIcE')
131 )
132
133 """
134 Runs a command as the client user.
135 """
136 def run(cmd):
137 return "su - ${user} -c " + shlex.quote(cmd)
138
139 screenshot_idx = 1
140
141 """
142 Takes screenshots on all clients.
143 """
144 def take_screenshots(screenshot_idx):
145 for client in clients:
146 client.node.screenshot(f"screen_{client.name}_{screenshot_idx}")
147 return screenshot_idx + 1
148
149 """
150 Sets up a client, waiting for the given barrier on completion.
151 """
152 def client_setup(client, servers, barrier):
153 client.node.wait_for_x()
154
155 # Configure Armagetron so we skip the tutorial.
156 client.node.succeed(
157 run("mkdir -p ~/.armagetronad/var"),
158 run(f"echo 'PLAYER_1 {client.name}' >> ~/.armagetronad/var/autoexec.cfg"),
159 run("echo 'FIRST_USE 0' >> ~/.armagetronad/var/autoexec.cfg")
160 )
161 for idx, srv in enumerate(servers):
162 client.node.succeed(
163 run(f"echo 'BOOKMARK_{idx+1}_ADDRESS {srv.address}' >> ~/.armagetronad/var/autoexec.cfg"),
164 run(f"echo 'BOOKMARK_{idx+1}_NAME {srv.name}' >> ~/.armagetronad/var/autoexec.cfg"),
165 run(f"echo 'BOOKMARK_{idx+1}_PORT {srv.port}' >> ~/.armagetronad/var/autoexec.cfg")
166 )
167
168 # Start Armagetron. Use the recording mode since it skips the splashscreen.
169 client.node.succeed(run("cd; ulimit -c unlimited; armagetronad --record test.aarec >&2 & disown"))
170 client.node.wait_until_succeeds(
171 run(
172 "${xdo "create_new_win-select_main_window" ''
173 search --onlyvisible --name "Armagetron Advanced"
174 windowfocus --sync
175 windowactivate --sync
176 ''}"
177 )
178 )
179
180 # Get into the multiplayer menu.
181 client.send_on('Armagetron Advanced', 'ret')
182 client.send_on('Play Game', 'ret')
183
184 # Online > LAN > Network Setup > Mates > Server Bookmarks
185 client.send_on('Multiplayer', 'down', 'down', 'down', 'down', 'ret')
186
187 barrier.wait()
188
189 # Start everything.
190 start_all()
191
192 # Get to the Server Bookmarks screen on both clients. This takes a while so do it asynchronously.
193 barrier = threading.Barrier(len(clients) + 1, timeout=600)
194 for client in clients:
195 threading.Thread(target=client_setup, args=(client, servers, barrier)).start()
196
197 # Wait for the servers to come up.
198 for srv in servers:
199 srv.node.wait_for_unit(f"armagetronad-{srv.name}")
200 srv.node.wait_until_succeeds(f"ss --numeric --udp --listening | grep -q {srv.port}")
201
202 # Make sure console commands work through the named pipe we created.
203 for srv in servers:
204 srv.node.succeed(
205 f"echo 'say Testing!' >> /var/lib/armagetronad/{srv.name}/input"
206 )
207 srv.node.succeed(
208 f"echo 'say Testing again!' >> /var/lib/armagetronad/{srv.name}/input"
209 )
210 srv.node.wait_until_succeeds(
211 f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Admin: Testing!'"
212 )
213 srv.node.wait_until_succeeds(
214 f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Admin: Testing again!'"
215 )
216
217 # Wait for the client setup to complete.
218 barrier.wait()
219
220 # Main testing loop. Iterates through each server bookmark and connects to them in sequence.
221 # Assumes that the game is currently on the Server Bookmarks screen.
222 for srv in servers:
223 screenshot_idx = take_screenshots(screenshot_idx)
224
225 # Connect both clients at once, one second apart.
226 for client in clients:
227 client.send('ret')
228 client.node.sleep(1)
229
230 # Wait for clients to connect
231 for client in clients:
232 srv.node.wait_until_succeeds(
233 f"journalctl -u armagetronad-{srv.name} -e | grep -q '{client.name}.*entered the game'"
234 )
235
236 # Wait for the match to start
237 srv.node.wait_until_succeeds(
238 f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Admin: {srv.welcome}'"
239 )
240 srv.node.wait_until_succeeds(
241 f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Admin: https://nixos.org'"
242 )
243 srv.node.wait_until_succeeds(
244 f"journalctl -u armagetronad-{srv.name} -e | grep -q 'Go (round 1 of 10)'"
245 )
246
247 # Wait for the players to die by running into the wall.
248 player1 = next(client for client in clients if client.name == srv.player1)
249 player2 = next(client for client in clients if client.name == srv.player2)
250 srv.node.wait_until_succeeds(
251 f"journalctl -u armagetronad-{srv.name} -e | grep -q '{player1.name}.*lost 4 points'"
252 )
253 srv.node.wait_until_succeeds(
254 f"journalctl -u armagetronad-{srv.name} -e | grep -q '{player2.name}.*lost 4 points'"
255 )
256 screenshot_idx = take_screenshots(screenshot_idx)
257
258 # Disconnect both clients from the server
259 for client in clients:
260 client.send('esc')
261 client.send_on('Menu', 'up', 'up', 'ret')
262 srv.node.wait_until_succeeds(
263 f"journalctl -u armagetronad-{srv.name} -e | grep -q '{client.name}.*left the game'"
264 )
265
266 # Next server.
267 for client in clients:
268 client.send_on('Server Bookmarks', 'down')
269
270 # Stop the servers
271 for srv in servers:
272 srv.node.succeed(
273 f"systemctl stop armagetronad-{srv.name}"
274 )
275 srv.node.wait_until_fails(f"ss --numeric --udp --listening | grep -q {srv.port}")
276 '';
277
278}