blob: 9773cfb0dd6778814058683550e1f520629e43b4 [file] [log] [blame]
Mohamed Heikalf746b57f2024-11-13 21:20:171#!/usr/bin/env vpython3
2# Copyright 2024 The Chromium Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import contextlib
7import datetime
Mohamed Heikalf746b57f2024-11-13 21:20:178import pathlib
9import unittest
10import os
Mohamed Heikalb752b772024-11-25 23:05:4411import signal
Mohamed Heikalf746b57f2024-11-13 21:20:1712import socket
13import subprocess
14import sys
Andrew Grieve0d6e8a752025-02-05 21:20:5015import tempfile
Mohamed Heikalf746b57f2024-11-13 21:20:1716import time
Mohamed Heikalf746b57f2024-11-13 21:20:1717
18import fast_local_dev_server as server
19
20sys.path.append(os.path.join(os.path.dirname(__file__), 'gyp'))
21from util import server_utils
22
23
24class RegexTest(unittest.TestCase):
25
26 def testBuildIdRegex(self):
Andrew Grieve0d6e8a752025-02-05 21:20:5027 self.assertRegex(server.FIRST_LOG_LINE.format(build_id='abc', outdir='PWD'),
Mohamed Heikalf746b57f2024-11-13 21:20:1728 server.BUILD_ID_RE)
29
30
Mohamed Heikalf11b6f32025-01-30 19:44:2931def sendMessage(message):
Mohamed Heikalf746b57f2024-11-13 21:20:1732 with contextlib.closing(socket.socket(socket.AF_UNIX)) as sock:
33 sock.settimeout(1)
34 sock.connect(server_utils.SOCKET_ADDRESS)
Mohamed Heikalf11b6f32025-01-30 19:44:2935 server_utils.SendMessage(sock, message)
Mohamed Heikalf746b57f2024-11-13 21:20:1736
37
Mohamed Heikalb752b772024-11-25 23:05:4438def pollServer():
39 try:
40 sendMessage({'message_type': server_utils.POLL_HEARTBEAT})
41 return True
42 except ConnectionRefusedError:
43 return False
44
45
Mohamed Heikal3b8c9552025-02-11 22:33:4046def shouldSkip():
47 if os.environ.get('ALLOW_EXTERNAL_BUILD_SERVER', None):
48 return False
49 return pollServer()
50
51
Andrew Grieved863d0f2024-12-13 20:13:0152def callServer(args, check=True):
Mohamed Heikalabf646e2024-12-12 16:06:0553 return subprocess.run([str(server_utils.SERVER_SCRIPT.absolute())] + args,
Mohamed Heikal6b56cf62024-12-10 23:14:5554 cwd=pathlib.Path(__file__).parent,
Andrew Grieved863d0f2024-12-13 20:13:0155 stdout=subprocess.PIPE,
Mohamed Heikalabf646e2024-12-12 16:06:0556 stderr=subprocess.STDOUT,
Mohamed Heikal6b56cf62024-12-10 23:14:5557 check=check,
Mohamed Heikal3b8c9552025-02-11 22:33:4058 text=True,
59 timeout=3)
Mohamed Heikalb752b772024-11-25 23:05:4460
61
Mohamed Heikalabf646e2024-12-12 16:06:0562@contextlib.contextmanager
63def blockingFifo(fifo_path='/tmp/.fast_local_dev_server_test.fifo'):
64 fifo_path = pathlib.Path(fifo_path)
65 try:
66 if not fifo_path.exists():
67 os.mkfifo(fifo_path)
68 yield fifo_path
69 finally:
70 # Write to the fifo nonblocking to unblock other end.
71 try:
72 pipe = os.open(fifo_path, os.O_WRONLY | os.O_NONBLOCK)
73 os.write(pipe, b'')
74 os.close(pipe)
75 except OSError:
76 # Can't open non-blocking an unconnected pipe for writing.
77 pass
78 fifo_path.unlink(missing_ok=True)
79
80
Andrew Grieved863d0f2024-12-13 20:13:0181class ServerStartedTest(unittest.TestCase):
Andrew Grieve0d6e8a752025-02-05 21:20:5082 build_id_counter = 0
83 task_name_counter = 0
84
85 def __init__(self, *args, **kwargs):
86 super().__init__(*args, **kwargs)
87 self._tty_path = None
88 self._build_id = None
Mohamed Heikalf746b57f2024-11-13 21:20:1789
90 def setUp(self):
Mohamed Heikal3b8c9552025-02-11 22:33:4091 if shouldSkip():
Mohamed Heikalf746b57f2024-11-13 21:20:1792 self.skipTest("Cannot run test when server already running.")
Mohamed Heikalf746b57f2024-11-13 21:20:1793 self._process = subprocess.Popen(
94 [server_utils.SERVER_SCRIPT.absolute(), '--exit-on-idle', '--quiet'],
95 start_new_session=True,
Mohamed Heikalb752b772024-11-25 23:05:4496 cwd=pathlib.Path(__file__).parent,
97 stdout=subprocess.PIPE,
98 stderr=subprocess.STDOUT,
99 text=True)
Mohamed Heikalf746b57f2024-11-13 21:20:17100 # pylint: disable=unused-variable
101 for attempt in range(5):
Mohamed Heikalb752b772024-11-25 23:05:44102 if pollServer():
Mohamed Heikalf746b57f2024-11-13 21:20:17103 break
Mohamed Heikalb752b772024-11-25 23:05:44104 time.sleep(0.05)
Mohamed Heikalf746b57f2024-11-13 21:20:17105
106 def tearDown(self):
Mohamed Heikalf746b57f2024-11-13 21:20:17107 self._process.terminate()
Mohamed Heikalb752b772024-11-25 23:05:44108 stdout, _ = self._process.communicate()
109 if stdout != '':
110 self.fail(f'build server should be silent but it output:\n{stdout}')
Mohamed Heikalf746b57f2024-11-13 21:20:17111
Andrew Grieve0d6e8a752025-02-05 21:20:50112 @contextlib.contextmanager
113 def _register_build(self):
114 with tempfile.NamedTemporaryFile() as f:
115 build_id = f'BUILD_ID_{ServerStartedTest.build_id_counter}'
116 os.environ['AUTONINJA_BUILD_ID'] = build_id
117 os.environ['AUTONINJA_STDOUT_NAME'] = f.name
118 ServerStartedTest.build_id_counter += 1
119 build_proc = subprocess.Popen(
120 [sys.executable, '-c', 'import time; time.sleep(100)'])
121 callServer(
122 ['--register-build', build_id, '--builder-pid',
123 str(build_proc.pid)])
124 self._tty_path = f.name
125 self._build_id = build_id
126 try:
127 yield
128 finally:
129 self._tty_path = None
130 self._build_id = None
131 del os.environ['AUTONINJA_BUILD_ID']
132 del os.environ['AUTONINJA_STDOUT_NAME']
133 build_proc.kill()
134 build_proc.wait()
135
Mohamed Heikal9984e432024-12-03 18:21:40136 def sendTask(self, cmd, stamp_path=None):
137 if stamp_path:
138 _stamp_file = pathlib.Path(stamp_path)
139 else:
140 _stamp_file = pathlib.Path('/tmp/.test.stamp')
Mohamed Heikalf746b57f2024-11-13 21:20:17141 _stamp_file.touch()
Mohamed Heikal9984e432024-12-03 18:21:40142
Andrew Grieve0d6e8a752025-02-05 21:20:50143 name_prefix = f'{self._build_id}-{ServerStartedTest.task_name_counter}'
Mohamed Heikalf746b57f2024-11-13 21:20:17144 sendMessage({
Andrew Grieve0d6e8a752025-02-05 21:20:50145 'name': f'{name_prefix}: {" ".join(cmd)}',
Mohamed Heikalf746b57f2024-11-13 21:20:17146 'message_type': server_utils.ADD_TASK,
147 'cmd': cmd,
148 # So that logfiles do not clutter cwd.
149 'cwd': '/tmp/',
Andrew Grieve0d6e8a752025-02-05 21:20:50150 'build_id': self._build_id,
Mohamed Heikalf746b57f2024-11-13 21:20:17151 'stamp_file': _stamp_file.name,
152 })
Andrew Grieve0d6e8a752025-02-05 21:20:50153 ServerStartedTest.task_name_counter += 1
Mohamed Heikalf746b57f2024-11-13 21:20:17154
Mohamed Heikalee1abc22024-11-14 21:50:31155 def getTtyContents(self):
Andrew Grieve0d6e8a752025-02-05 21:20:50156 return pathlib.Path(self._tty_path).read_text()
Mohamed Heikalee1abc22024-11-14 21:50:31157
Mohamed Heikalf746b57f2024-11-13 21:20:17158 def getBuildInfo(self):
Andrew Grieve0d6e8a752025-02-05 21:20:50159 build_info = server.query_build_info(self._build_id)['builds'][0]
Mohamed Heikalf746b57f2024-11-13 21:20:17160 pending_tasks = build_info['pending_tasks']
161 completed_tasks = build_info['completed_tasks']
Mohamed Heikalee1abc22024-11-14 21:50:31162 return pending_tasks, completed_tasks
Mohamed Heikalf746b57f2024-11-13 21:20:17163
164 def waitForTasksDone(self, timeout_seconds=3):
165 timeout_duration = datetime.timedelta(seconds=timeout_seconds)
166 start_time = datetime.datetime.now()
Mohamed Heikalf746b57f2024-11-13 21:20:17167 while True:
Mohamed Heikalee1abc22024-11-14 21:50:31168 pending_tasks, completed_tasks = self.getBuildInfo()
Mohamed Heikalf746b57f2024-11-13 21:20:17169
170 if completed_tasks > 0 and pending_tasks == 0:
Mohamed Heikalee1abc22024-11-14 21:50:31171 return
Mohamed Heikalf746b57f2024-11-13 21:20:17172
173 current_time = datetime.datetime.now()
174 duration = current_time - start_time
175 if duration > timeout_duration:
Mohamed Heikal08b467e02025-01-27 20:54:25176 raise TimeoutError('Timed out waiting for pending tasks ' +
177 f'[{pending_tasks}/{pending_tasks+completed_tasks}]')
Mohamed Heikalf746b57f2024-11-13 21:20:17178 time.sleep(0.1)
179
180 def testRunsQuietTask(self):
Andrew Grieve0d6e8a752025-02-05 21:20:50181 with self._register_build():
182 self.sendTask(['true'])
183 self.waitForTasksDone()
184 self.assertEqual(self.getTtyContents(), '')
Mohamed Heikalf746b57f2024-11-13 21:20:17185
186 def testRunsNoisyTask(self):
Andrew Grieve0d6e8a752025-02-05 21:20:50187 with self._register_build():
188 self.sendTask(['echo', 'some_output'])
189 self.waitForTasksDone()
190 tty_contents = self.getTtyContents()
191 self.assertIn('some_output', tty_contents)
Mohamed Heikalf746b57f2024-11-13 21:20:17192
Mohamed Heikal9984e432024-12-03 18:21:40193 def testStampFileDeletedOnFailedTask(self):
Andrew Grieve0d6e8a752025-02-05 21:20:50194 with self._register_build():
195 stamp_file = pathlib.Path('/tmp/.failed_task.stamp')
196 self.sendTask(['echo', 'some_output'], stamp_path=stamp_file)
197 self.waitForTasksDone()
198 self.assertFalse(stamp_file.exists())
Mohamed Heikal9984e432024-12-03 18:21:40199
200 def testStampFileNotDeletedOnSuccess(self):
Andrew Grieve0d6e8a752025-02-05 21:20:50201 with self._register_build():
202 stamp_file = pathlib.Path('/tmp/.successful_task.stamp')
203 self.sendTask(['true'], stamp_path=stamp_file)
204 self.waitForTasksDone()
205 self.assertTrue(stamp_file.exists())
Mohamed Heikalb752b772024-11-25 23:05:44206
207 def testWaitForBuildServerCall(self):
Andrew Grieve0d6e8a752025-02-05 21:20:50208 with self._register_build():
209 callServer(['--wait-for-build', self._build_id])
210 self.assertEqual(self.getTtyContents(), '')
Mohamed Heikalb752b772024-11-25 23:05:44211
Mohamed Heikalf11b6f32025-01-30 19:44:29212 def testWaitForIdleServerCall(self):
Andrew Grieve0d6e8a752025-02-05 21:20:50213 with self._register_build():
214 self.sendTask(['true'])
215 proc_result = callServer(['--wait-for-idle'])
216 self.assertIn('All', proc_result.stdout)
217 self.assertEqual('', self.getTtyContents())
Mohamed Heikalf11b6f32025-01-30 19:44:29218
Mohamed Heikalb752b772024-11-25 23:05:44219 def testCancelBuildServerCall(self):
Andrew Grieve0d6e8a752025-02-05 21:20:50220 with self._register_build():
221 callServer(['--cancel-build', self._build_id])
222 self.assertEqual(self.getTtyContents(), '')
Mohamed Heikalb752b772024-11-25 23:05:44223
Mohamed Heikal6b56cf62024-12-10 23:14:55224 def testBuildStatusServerCall(self):
Andrew Grieve0d6e8a752025-02-05 21:20:50225 with self._register_build():
226 proc_result = callServer(['--print-status', self._build_id])
227 self.assertEqual(proc_result.stdout, '')
Mohamed Heikal6b56cf62024-12-10 23:14:55228
Andrew Grieve0d6e8a752025-02-05 21:20:50229 proc_result = callServer(['--print-status-all'])
230 self.assertIn(self._build_id, proc_result.stdout)
Andrew Grieved863d0f2024-12-13 20:13:01231
Andrew Grieve0d6e8a752025-02-05 21:20:50232 self.sendTask(['true'])
233 self.waitForTasksDone()
Andrew Grieved863d0f2024-12-13 20:13:01234
Andrew Grieve0d6e8a752025-02-05 21:20:50235 proc_result = callServer(['--print-status', self._build_id])
Andrew Grieve2f123a02025-03-20 18:13:06236 self.assertEqual('', proc_result.stdout)
Andrew Grieved863d0f2024-12-13 20:13:01237
Andrew Grieve0d6e8a752025-02-05 21:20:50238 proc_result = callServer(['--print-status-all'])
239 self.assertIn('has 1 registered build', proc_result.stdout)
Andrew Grievef12da2c62025-06-24 14:07:51240 self.assertIn('1/1', proc_result.stdout)
Mohamed Heikal6b56cf62024-12-10 23:14:55241
Andrew Grieve0d6e8a752025-02-05 21:20:50242 with blockingFifo() as fifo_path:
243 # cat gets stuck until we open the other end of the fifo.
244 self.sendTask(['cat', str(fifo_path)])
245 proc_result = callServer(['--print-status', self._build_id])
Andrew Grieve2f123a02025-03-20 18:13:06246 self.assertIn('is still 1 static analysis job', proc_result.stdout)
Andrew Grieve0d6e8a752025-02-05 21:20:50247 self.assertIn('--wait-for-idle', proc_result.stdout)
Andrew Grieve2f123a02025-03-20 18:13:06248 proc_result = callServer(['--print-status-all'])
Andrew Grievef12da2c62025-06-24 14:07:51249 self.assertIn('1/2', proc_result.stdout)
Mohamed Heikal6b56cf62024-12-10 23:14:55250
Andrew Grieve0d6e8a752025-02-05 21:20:50251 self.waitForTasksDone()
252 callServer(['--cancel-build', self._build_id])
253 self.waitForTasksDone()
254 proc_result = callServer(['--print-status', self._build_id])
Andrew Grieve2f123a02025-03-20 18:13:06255 self.assertEqual('', proc_result.stdout)
Mohamed Heikal6b56cf62024-12-10 23:14:55256
Andrew Grieved863d0f2024-12-13 20:13:01257 proc_result = callServer(['--print-status-all'])
Andrew Grievef12da2c62025-06-24 14:07:51258 self.assertIn('Main build completed', proc_result.stdout)
Andrew Grieved863d0f2024-12-13 20:13:01259
Mohamed Heikalabf646e2024-12-12 16:06:05260 def testServerCancelsRunningTasks(self):
261 output_stamp = pathlib.Path('/tmp/.deleteme.stamp')
262 with blockingFifo() as fifo_path:
263 self.assertFalse(output_stamp.exists())
264 # dd blocks on fifo so task never finishes inside with block.
Andrew Grieve0d6e8a752025-02-05 21:20:50265 with self._register_build():
266 self.sendTask(['dd', f'if={str(fifo_path)}', f'of={str(output_stamp)}'])
267 callServer(['--cancel-build', self._build_id])
268 self.waitForTasksDone()
Mohamed Heikalabf646e2024-12-12 16:06:05269 self.assertFalse(output_stamp.exists())
270
Mohamed Heikalb752b772024-11-25 23:05:44271 def testKeyboardInterrupt(self):
272 os.kill(self._process.pid, signal.SIGINT)
273 self._process.wait(timeout=1)
274
Mohamed Heikalf746b57f2024-11-13 21:20:17275
Andrew Grieved863d0f2024-12-13 20:13:01276class ServerNotStartedTest(unittest.TestCase):
277
Mohamed Heikal3b8c9552025-02-11 22:33:40278 def setUp(self):
279 if pollServer():
280 self.skipTest("Cannot run test when server already running.")
281
Andrew Grieved863d0f2024-12-13 20:13:01282 def testWaitForBuildServerCall(self):
Andrew Grieve0d6e8a752025-02-05 21:20:50283 proc_result = callServer(['--wait-for-build', 'invalid-build-id'])
Andrew Grieved863d0f2024-12-13 20:13:01284 self.assertIn('No server running', proc_result.stdout)
285
286 def testBuildStatusServerCall(self):
287 proc_result = callServer(['--print-status-all'])
288 self.assertIn('No server running', proc_result.stdout)
289
290
Mohamed Heikalf746b57f2024-11-13 21:20:17291if __name__ == '__main__':
292 # Suppress logging messages.
293 unittest.main(buffer=True)