blob: e412ab03e49cce56091219082cd11f3d2ae97d57 [file] [log] [blame]
Zhuoyao Zhang53359552024-09-16 23:58:11 +00001# Copyright 2024, The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Unittests for DaemonManager."""
16
17import logging
18import multiprocessing
19import os
20import pathlib
21import signal
22import subprocess
23import sys
24import tempfile
25import time
26import unittest
27from unittest import mock
28from edit_monitor import daemon_manager
Zhuoyao Zhangba64f312024-10-14 20:32:53 +000029from proto import edit_event_pb2
Zhuoyao Zhang53359552024-09-16 23:58:11 +000030
Zhuoyao Zhangd28da5c2024-09-24 19:46:12 +000031
Zhuoyao Zhang53359552024-09-16 23:58:11 +000032TEST_BINARY_FILE = '/path/to/test_binary'
33TEST_PID_FILE_PATH = (
34 '587239c2d1050afdf54512e2d799f3b929f86b43575eb3c7b4bab105dd9bd25e.lock'
35)
36
37
Zhuoyao Zhang4d485592024-09-17 21:14:38 +000038def simple_daemon(output_file):
Zhuoyao Zhang53359552024-09-16 23:58:11 +000039 with open(output_file, 'w') as f:
40 f.write('running daemon target')
41
42
43def long_running_daemon():
44 while True:
45 time.sleep(1)
46
47
Zhuoyao Zhangdc2840d2024-09-19 23:29:27 +000048def memory_consume_daemon_target(size_mb):
49 try:
50 size_bytes = size_mb * 1024 * 1024
51 dummy_data = bytearray(size_bytes)
52 time.sleep(10)
53 except MemoryError:
54 print(f'Process failed to allocate {size_mb} MB of memory.')
55
56
57def cpu_consume_daemon_target(target_usage_percent):
58 while True:
59 start_time = time.time()
60 while time.time() - start_time < target_usage_percent / 100:
61 pass # Busy loop to consume CPU
62
63 # Sleep to reduce CPU usage
64 time.sleep(1 - target_usage_percent / 100)
65
66
Zhuoyao Zhang53359552024-09-16 23:58:11 +000067class DaemonManagerTest(unittest.TestCase):
68
69 @classmethod
70 def setUpClass(cls):
71 super().setUpClass()
72 # Configure to print logging to stdout.
73 logging.basicConfig(filename=None, level=logging.DEBUG)
74 console = logging.StreamHandler(sys.stdout)
75 logging.getLogger('').addHandler(console)
76
77 def setUp(self):
78 super().setUp()
79 self.original_tempdir = tempfile.tempdir
80 self.working_dir = tempfile.TemporaryDirectory()
81 # Sets the tempdir under the working dir so any temp files created during
82 # tests will be cleaned.
83 tempfile.tempdir = self.working_dir.name
Zhuoyao Zhangd1c4a8b2024-11-06 21:48:45 +000084 self.patch = mock.patch.dict(
85 os.environ, {'ENABLE_ANDROID_EDIT_MONITOR': 'true'})
Zhuoyao Zhang3ca7cef2024-10-31 22:07:31 +000086 self.patch.start()
Zhuoyao Zhang53359552024-09-16 23:58:11 +000087
88 def tearDown(self):
89 # Cleans up any child processes left by the tests.
90 self._cleanup_child_processes()
91 self.working_dir.cleanup()
92 # Restores tempdir.
93 tempfile.tempdir = self.original_tempdir
Zhuoyao Zhang3ca7cef2024-10-31 22:07:31 +000094 self.patch.stop()
Zhuoyao Zhang53359552024-09-16 23:58:11 +000095 super().tearDown()
96
Zhuoyao Zhang4d485592024-09-17 21:14:38 +000097 def test_start_success_with_no_existing_instance(self):
98 self.assert_run_simple_daemon_success()
99
100 def test_start_success_with_existing_instance_running(self):
Zhuoyao Zhangd28da5c2024-09-24 19:46:12 +0000101 # Create a running daemon subprocess
102 p = self._create_fake_deamon_process()
Zhuoyao Zhang4d485592024-09-17 21:14:38 +0000103
104 self.assert_run_simple_daemon_success()
Zhuoyao Zhang4d485592024-09-17 21:14:38 +0000105
106 def test_start_success_with_existing_instance_already_dead(self):
107 # Create a pidfile with pid that does not exist.
108 pid_file_path_dir = pathlib.Path(self.working_dir.name).joinpath(
109 'edit_monitor'
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000110 )
Zhuoyao Zhang4d485592024-09-17 21:14:38 +0000111 pid_file_path_dir.mkdir(parents=True, exist_ok=True)
112 with open(pid_file_path_dir.joinpath(TEST_PID_FILE_PATH), 'w') as f:
113 f.write('123456')
114
115 self.assert_run_simple_daemon_success()
116
117 def test_start_success_with_existing_instance_from_different_binary(self):
118 # First start an instance based on "some_binary_path"
119 existing_dm = daemon_manager.DaemonManager(
Zhuoyao Zhangdc2840d2024-09-19 23:29:27 +0000120 'some_binary_path',
Zhuoyao Zhang4d485592024-09-17 21:14:38 +0000121 daemon_target=long_running_daemon,
122 )
123 existing_dm.start()
124
125 self.assert_run_simple_daemon_success()
126 existing_dm.stop()
127
Zhuoyao Zhangd28da5c2024-09-24 19:46:12 +0000128 def test_start_return_directly_if_block_sign_exists(self):
129 # Creates the block sign.
130 pathlib.Path(self.working_dir.name).joinpath(
131 daemon_manager.BLOCK_SIGN_FILE
132 ).touch()
133
134 dm = daemon_manager.DaemonManager(TEST_BINARY_FILE)
135 dm.start()
Zhuoyao Zhang3ca7cef2024-10-31 22:07:31 +0000136
137 # Verify no daemon process is started.
138 self.assertIsNone(dm.daemon_process)
139
Zhuoyao Zhangd1c4a8b2024-11-06 21:48:45 +0000140 @mock.patch.dict(os.environ, {'ENABLE_ANDROID_EDIT_MONITOR': 'false'}, clear=True)
Zhuoyao Zhang3ca7cef2024-10-31 22:07:31 +0000141 def test_start_return_directly_if_disabled(self):
142 dm = daemon_manager.DaemonManager(TEST_BINARY_FILE)
143 dm.start()
144
Zhuoyao Zhangd28da5c2024-09-24 19:46:12 +0000145 # Verify no daemon process is started.
146 self.assertIsNone(dm.daemon_process)
147
Zhuoyao Zhang05e28fa2024-10-04 21:58:39 +0000148 def test_start_return_directly_if_in_cog_env(self):
149 dm = daemon_manager.DaemonManager(
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000150 '/google/cog/cloud/user/workspace/edit_monitor'
151 )
Zhuoyao Zhang05e28fa2024-10-04 21:58:39 +0000152 dm.start()
Zhuoyao Zhang3ca7cef2024-10-31 22:07:31 +0000153
Zhuoyao Zhang05e28fa2024-10-04 21:58:39 +0000154 # Verify no daemon process is started.
155 self.assertIsNone(dm.daemon_process)
156
Zhuoyao Zhang4d485592024-09-17 21:14:38 +0000157 @mock.patch('os.kill')
158 def test_start_failed_to_kill_existing_instance(self, mock_kill):
159 mock_kill.side_effect = OSError('Unknown OSError')
160 pid_file_path_dir = pathlib.Path(self.working_dir.name).joinpath(
161 'edit_monitor'
162 )
163 pid_file_path_dir.mkdir(parents=True, exist_ok=True)
164 with open(pid_file_path_dir.joinpath(TEST_PID_FILE_PATH), 'w') as f:
165 f.write('123456')
166
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000167 fake_cclient = FakeClearcutClient()
168 with self.assertRaises(OSError):
169 dm = daemon_manager.DaemonManager(TEST_BINARY_FILE, cclient=fake_cclient)
Zhuoyao Zhangd28da5c2024-09-24 19:46:12 +0000170 dm.start()
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000171 self._assert_error_event_logged(
172 fake_cclient, edit_event_pb2.EditEvent.FAILED_TO_START_EDIT_MONITOR
173 )
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000174
175 def test_start_failed_to_write_pidfile(self):
176 pid_file_path_dir = pathlib.Path(self.working_dir.name).joinpath(
177 'edit_monitor'
178 )
179 pid_file_path_dir.mkdir(parents=True, exist_ok=True)
180 # Makes the directory read-only so write pidfile will fail.
181 os.chmod(pid_file_path_dir, 0o555)
182
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000183 fake_cclient = FakeClearcutClient()
184 with self.assertRaises(PermissionError):
185 dm = daemon_manager.DaemonManager(TEST_BINARY_FILE, cclient=fake_cclient)
Zhuoyao Zhangd28da5c2024-09-24 19:46:12 +0000186 dm.start()
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000187 self._assert_error_event_logged(
188 fake_cclient, edit_event_pb2.EditEvent.FAILED_TO_START_EDIT_MONITOR
189 )
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000190
191 def test_start_failed_to_start_daemon_process(self):
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000192 fake_cclient = FakeClearcutClient()
193 with self.assertRaises(TypeError):
Zhuoyao Zhangd28da5c2024-09-24 19:46:12 +0000194 dm = daemon_manager.DaemonManager(
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000195 TEST_BINARY_FILE,
196 daemon_target='wrong_target',
197 daemon_args=(1),
198 cclient=fake_cclient,
Zhuoyao Zhangd28da5c2024-09-24 19:46:12 +0000199 )
200 dm.start()
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000201 self._assert_error_event_logged(
202 fake_cclient, edit_event_pb2.EditEvent.FAILED_TO_START_EDIT_MONITOR
203 )
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000204
Zhuoyao Zhang78fd0762024-11-18 22:15:42 +0000205 @mock.patch('os.execv')
206 def test_monitor_reboot_with_high_memory_usage(self, mock_execv):
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000207 fake_cclient = FakeClearcutClient()
Zhuoyao Zhang78fd0762024-11-18 22:15:42 +0000208 binary_file = tempfile.NamedTemporaryFile(
209 dir=self.working_dir.name, delete=False
210 )
Zhuoyao Zhang69882722024-11-15 18:32:18 +0000211
Zhuoyao Zhangdc2840d2024-09-19 23:29:27 +0000212 dm = daemon_manager.DaemonManager(
Zhuoyao Zhang78fd0762024-11-18 22:15:42 +0000213 binary_file.name,
Zhuoyao Zhangdc2840d2024-09-19 23:29:27 +0000214 daemon_target=memory_consume_daemon_target,
215 daemon_args=(2,),
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000216 cclient=fake_cclient,
Zhuoyao Zhangdc2840d2024-09-19 23:29:27 +0000217 )
Zhuoyao Zhang69882722024-11-15 18:32:18 +0000218 # set the fake total_memory_size
219 dm.total_memory_size = 100 * 1024 *1024
Zhuoyao Zhangdc2840d2024-09-19 23:29:27 +0000220 dm.start()
Zhuoyao Zhang69882722024-11-15 18:32:18 +0000221 dm.monitor_daemon(interval=1)
Zhuoyao Zhangdc2840d2024-09-19 23:29:27 +0000222
Zhuoyao Zhang69882722024-11-15 18:32:18 +0000223 self.assertTrue(dm.max_memory_usage >= 0.02)
Zhuoyao Zhangdc2840d2024-09-19 23:29:27 +0000224 self.assert_no_subprocess_running()
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000225 self._assert_error_event_logged(
226 fake_cclient,
Zhuoyao Zhang585b4342024-11-12 22:46:43 +0000227 edit_event_pb2.EditEvent.KILLED_DUE_TO_EXCEEDED_MEMORY_USAGE,
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000228 )
Zhuoyao Zhang78fd0762024-11-18 22:15:42 +0000229 mock_execv.assert_called_once()
Zhuoyao Zhangdc2840d2024-09-19 23:29:27 +0000230
231 def test_monitor_daemon_subprocess_killed_high_cpu_usage(self):
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000232 fake_cclient = FakeClearcutClient()
Zhuoyao Zhangdc2840d2024-09-19 23:29:27 +0000233 dm = daemon_manager.DaemonManager(
234 TEST_BINARY_FILE,
235 daemon_target=cpu_consume_daemon_target,
236 daemon_args=(20,),
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000237 cclient=fake_cclient,
Zhuoyao Zhangdc2840d2024-09-19 23:29:27 +0000238 )
239 dm.start()
240 dm.monitor_daemon(interval=1, cpu_threshold=20)
241
242 self.assertTrue(dm.max_cpu_usage >= 20)
243 self.assert_no_subprocess_running()
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000244 self._assert_error_event_logged(
245 fake_cclient,
Zhuoyao Zhang585b4342024-11-12 22:46:43 +0000246 edit_event_pb2.EditEvent.KILLED_DUE_TO_EXCEEDED_CPU_USAGE,
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000247 )
Zhuoyao Zhangdc2840d2024-09-19 23:29:27 +0000248
249 @mock.patch('subprocess.check_output')
250 def test_monitor_daemon_failed_does_not_matter(self, mock_output):
251 mock_output.side_effect = OSError('Unknown OSError')
252 self.assert_run_simple_daemon_success()
253
Zhuoyao Zhang205a2fc2024-09-20 18:19:59 +0000254 @mock.patch('os.execv')
255 def test_monitor_daemon_reboot_triggered(self, mock_execv):
256 binary_file = tempfile.NamedTemporaryFile(
257 dir=self.working_dir.name, delete=False
258 )
259
260 dm = daemon_manager.DaemonManager(
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000261 binary_file.name,
262 daemon_target=long_running_daemon,
Zhuoyao Zhang205a2fc2024-09-20 18:19:59 +0000263 )
264 dm.start()
265 dm.monitor_daemon(reboot_timeout=0.5)
266 mock_execv.assert_called_once()
267
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000268 def test_stop_success(self):
269 dm = daemon_manager.DaemonManager(
270 TEST_BINARY_FILE, daemon_target=long_running_daemon
271 )
272 dm.start()
273 dm.stop()
274
275 self.assert_no_subprocess_running()
276 self.assertFalse(dm.pid_file_path.exists())
277
278 @mock.patch('os.kill')
279 def test_stop_failed_to_kill_daemon_process(self, mock_kill):
280 mock_kill.side_effect = OSError('Unknown OSError')
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000281 fake_cclient = FakeClearcutClient()
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000282 dm = daemon_manager.DaemonManager(
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000283 TEST_BINARY_FILE,
284 daemon_target=long_running_daemon,
285 cclient=fake_cclient,
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000286 )
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000287
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000288 with self.assertRaises(SystemExit):
289 dm.start()
290 dm.stop()
291 self.assertTrue(dm.daemon_process.is_alive())
292 self.assertTrue(dm.pid_file_path.exists())
293 self._assert_error_event_logged(
294 fake_cclient, edit_event_pb2.EditEvent.FAILED_TO_STOP_EDIT_MONITOR
295 )
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000296
297 @mock.patch('os.remove')
298 def test_stop_failed_to_remove_pidfile(self, mock_remove):
299 mock_remove.side_effect = OSError('Unknown OSError')
300
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000301 fake_cclient = FakeClearcutClient()
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000302 dm = daemon_manager.DaemonManager(
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000303 TEST_BINARY_FILE,
304 daemon_target=long_running_daemon,
305 cclient=fake_cclient,
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000306 )
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000307
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000308 with self.assertRaises(SystemExit):
309 dm.start()
310 dm.stop()
311 self.assert_no_subprocess_running()
312 self.assertTrue(dm.pid_file_path.exists())
313
314 self._assert_error_event_logged(
315 fake_cclient, edit_event_pb2.EditEvent.FAILED_TO_STOP_EDIT_MONITOR
316 )
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000317
Zhuoyao Zhang205a2fc2024-09-20 18:19:59 +0000318 @mock.patch('os.execv')
319 def test_reboot_success(self, mock_execv):
320 binary_file = tempfile.NamedTemporaryFile(
321 dir=self.working_dir.name, delete=False
322 )
323
324 dm = daemon_manager.DaemonManager(
325 binary_file.name, daemon_target=long_running_daemon
326 )
327 dm.start()
328 dm.reboot()
329
330 # Verifies the old process is stopped
331 self.assert_no_subprocess_running()
332 self.assertFalse(dm.pid_file_path.exists())
333
334 mock_execv.assert_called_once()
335
336 @mock.patch('os.execv')
337 def test_reboot_binary_no_longer_exists(self, mock_execv):
338 dm = daemon_manager.DaemonManager(
339 TEST_BINARY_FILE, daemon_target=long_running_daemon
340 )
341 dm.start()
342
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000343 with self.assertRaises(SystemExit):
Zhuoyao Zhang205a2fc2024-09-20 18:19:59 +0000344 dm.reboot()
345 mock_execv.assert_not_called()
346 self.assertEqual(cm.exception.code, 0)
347
348 @mock.patch('os.execv')
349 def test_reboot_failed(self, mock_execv):
350 mock_execv.side_effect = OSError('Unknown OSError')
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000351 fake_cclient = FakeClearcutClient()
Zhuoyao Zhang205a2fc2024-09-20 18:19:59 +0000352 binary_file = tempfile.NamedTemporaryFile(
353 dir=self.working_dir.name, delete=False
354 )
355
356 dm = daemon_manager.DaemonManager(
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000357 binary_file.name,
358 daemon_target=long_running_daemon,
359 cclient=fake_cclient,
Zhuoyao Zhang205a2fc2024-09-20 18:19:59 +0000360 )
361 dm.start()
362
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000363 with self.assertRaises(SystemExit):
Zhuoyao Zhang205a2fc2024-09-20 18:19:59 +0000364 dm.reboot()
365 self.assertEqual(cm.exception.code, 1)
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000366 self._assert_error_event_logged(
367 fake_cclient, edit_event_pb2.EditEvent.FAILED_TO_REBOOT_EDIT_MONITOR
368 )
Zhuoyao Zhang205a2fc2024-09-20 18:19:59 +0000369
Zhuoyao Zhang130da8e2024-12-05 23:16:43 +0000370 @mock.patch('subprocess.check_output')
371 def test_cleanup_success(self, mock_check_output):
372 p = self._create_fake_deamon_process()
373 fake_cclient = FakeClearcutClient()
374 mock_check_output.return_value = f'user {p.pid} 1 1 1 1 1 edit_monitor arg'
375
376 dm = daemon_manager.DaemonManager(
377 TEST_BINARY_FILE,
378 daemon_target=long_running_daemon,
379 cclient=fake_cclient,
380 )
381 dm.cleanup()
382
383 self.assertFalse(p.is_alive())
384 self.assertTrue(
385 pathlib.Path(self.working_dir.name)
386 .joinpath(daemon_manager.BLOCK_SIGN_FILE)
387 .exists()
388 )
389
Zhuoyao Zhang4d485592024-09-17 21:14:38 +0000390 def assert_run_simple_daemon_success(self):
391 damone_output_file = tempfile.NamedTemporaryFile(
392 dir=self.working_dir.name, delete=False
393 )
394 dm = daemon_manager.DaemonManager(
395 TEST_BINARY_FILE,
396 daemon_target=simple_daemon,
397 daemon_args=(damone_output_file.name,),
398 )
399 dm.start()
Zhuoyao Zhangdc2840d2024-09-19 23:29:27 +0000400 dm.monitor_daemon(interval=1)
Zhuoyao Zhang4d485592024-09-17 21:14:38 +0000401
402 # Verifies the expected pid file is created.
403 expected_pid_file_path = pathlib.Path(self.working_dir.name).joinpath(
404 'edit_monitor', TEST_PID_FILE_PATH
405 )
406 self.assertTrue(expected_pid_file_path.exists())
407
408 # Verify the daemon process is executed successfully.
409 with open(damone_output_file.name, 'r') as f:
410 contents = f.read()
411 self.assertEqual(contents, 'running daemon target')
412
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000413 def assert_no_subprocess_running(self):
414 child_pids = self._get_child_processes(os.getpid())
415 for child_pid in child_pids:
416 self.assertFalse(
417 self._is_process_alive(child_pid), f'process {child_pid} still alive'
418 )
419
Zhuoyao Zhangd28da5c2024-09-24 19:46:12 +0000420 def _get_child_processes(self, parent_pid: int) -> list[int]:
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000421 try:
422 output = subprocess.check_output(
423 ['ps', '-o', 'pid,ppid', '--no-headers'], text=True
424 )
425
426 child_processes = []
427 for line in output.splitlines():
428 pid, ppid = line.split()
429 if int(ppid) == parent_pid:
430 child_processes.append(int(pid))
431 return child_processes
432 except subprocess.CalledProcessError as e:
433 self.fail(f'failed to get child process, error: {e}')
434
Zhuoyao Zhangd28da5c2024-09-24 19:46:12 +0000435 def _is_process_alive(self, pid: int) -> bool:
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000436 try:
437 output = subprocess.check_output(
438 ['ps', '-p', str(pid), '-o', 'state='], text=True
439 ).strip()
440 state = output.split()[0]
441 return state != 'Z' # Check if the state is not 'Z' (zombie)
442 except subprocess.CalledProcessError:
443 return False
444
445 def _cleanup_child_processes(self):
446 child_pids = self._get_child_processes(os.getpid())
447 for child_pid in child_pids:
448 try:
449 os.kill(child_pid, signal.SIGKILL)
450 except ProcessLookupError:
451 # process already terminated
452 pass
453
Zhuoyao Zhangd28da5c2024-09-24 19:46:12 +0000454 def _create_fake_deamon_process(
455 self, name: str = ''
456 ) -> multiprocessing.Process:
457 # Create a long running subprocess
458 p = multiprocessing.Process(target=long_running_daemon)
459 p.start()
460
461 # Create the pidfile with the subprocess pid
462 pid_file_path_dir = pathlib.Path(self.working_dir.name).joinpath(
463 'edit_monitor'
464 )
465 pid_file_path_dir.mkdir(parents=True, exist_ok=True)
466 with open(pid_file_path_dir.joinpath(name + 'pid.lock'), 'w') as f:
467 f.write(str(p.pid))
468 return p
469
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000470 def _assert_error_event_logged(self, fake_cclient, error_type):
471 error_events = fake_cclient.get_sent_events()
472 self.assertEquals(len(error_events), 1)
473 self.assertEquals(
474 edit_event_pb2.EditEvent.FromString(
475 error_events[0].source_extension
476 ).edit_monitor_error_event.error_type,
477 error_type,
478 )
479
480
481class FakeClearcutClient:
482
483 def __init__(self):
484 self.pending_log_events = []
485 self.sent_log_event = []
486
487 def log(self, log_event):
488 self.pending_log_events.append(log_event)
489
490 def flush_events(self):
491 self.sent_log_event.extend(self.pending_log_events)
492 self.pending_log_events.clear()
493
494 def get_sent_events(self):
495 return self.sent_log_event + self.pending_log_events
496
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000497
498if __name__ == '__main__':
499 unittest.main()