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