blob: 350739d450191dd7f5ef8b06099588f4c900d261 [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 Zhang4d485592024-09-17 21:14:38 +0000370 def assert_run_simple_daemon_success(self):
371 damone_output_file = tempfile.NamedTemporaryFile(
372 dir=self.working_dir.name, delete=False
373 )
374 dm = daemon_manager.DaemonManager(
375 TEST_BINARY_FILE,
376 daemon_target=simple_daemon,
377 daemon_args=(damone_output_file.name,),
378 )
379 dm.start()
Zhuoyao Zhangdc2840d2024-09-19 23:29:27 +0000380 dm.monitor_daemon(interval=1)
Zhuoyao Zhang4d485592024-09-17 21:14:38 +0000381
382 # Verifies the expected pid file is created.
383 expected_pid_file_path = pathlib.Path(self.working_dir.name).joinpath(
384 'edit_monitor', TEST_PID_FILE_PATH
385 )
386 self.assertTrue(expected_pid_file_path.exists())
387
388 # Verify the daemon process is executed successfully.
389 with open(damone_output_file.name, 'r') as f:
390 contents = f.read()
391 self.assertEqual(contents, 'running daemon target')
392
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000393 def assert_no_subprocess_running(self):
394 child_pids = self._get_child_processes(os.getpid())
395 for child_pid in child_pids:
396 self.assertFalse(
397 self._is_process_alive(child_pid), f'process {child_pid} still alive'
398 )
399
Zhuoyao Zhangd28da5c2024-09-24 19:46:12 +0000400 def _get_child_processes(self, parent_pid: int) -> list[int]:
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000401 try:
402 output = subprocess.check_output(
403 ['ps', '-o', 'pid,ppid', '--no-headers'], text=True
404 )
405
406 child_processes = []
407 for line in output.splitlines():
408 pid, ppid = line.split()
409 if int(ppid) == parent_pid:
410 child_processes.append(int(pid))
411 return child_processes
412 except subprocess.CalledProcessError as e:
413 self.fail(f'failed to get child process, error: {e}')
414
Zhuoyao Zhangd28da5c2024-09-24 19:46:12 +0000415 def _is_process_alive(self, pid: int) -> bool:
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000416 try:
417 output = subprocess.check_output(
418 ['ps', '-p', str(pid), '-o', 'state='], text=True
419 ).strip()
420 state = output.split()[0]
421 return state != 'Z' # Check if the state is not 'Z' (zombie)
422 except subprocess.CalledProcessError:
423 return False
424
425 def _cleanup_child_processes(self):
426 child_pids = self._get_child_processes(os.getpid())
427 for child_pid in child_pids:
428 try:
429 os.kill(child_pid, signal.SIGKILL)
430 except ProcessLookupError:
431 # process already terminated
432 pass
433
Zhuoyao Zhangd28da5c2024-09-24 19:46:12 +0000434 def _create_fake_deamon_process(
435 self, name: str = ''
436 ) -> multiprocessing.Process:
437 # Create a long running subprocess
438 p = multiprocessing.Process(target=long_running_daemon)
439 p.start()
440
441 # Create the pidfile with the subprocess pid
442 pid_file_path_dir = pathlib.Path(self.working_dir.name).joinpath(
443 'edit_monitor'
444 )
445 pid_file_path_dir.mkdir(parents=True, exist_ok=True)
446 with open(pid_file_path_dir.joinpath(name + 'pid.lock'), 'w') as f:
447 f.write(str(p.pid))
448 return p
449
Zhuoyao Zhangba64f312024-10-14 20:32:53 +0000450 def _assert_error_event_logged(self, fake_cclient, error_type):
451 error_events = fake_cclient.get_sent_events()
452 self.assertEquals(len(error_events), 1)
453 self.assertEquals(
454 edit_event_pb2.EditEvent.FromString(
455 error_events[0].source_extension
456 ).edit_monitor_error_event.error_type,
457 error_type,
458 )
459
460
461class FakeClearcutClient:
462
463 def __init__(self):
464 self.pending_log_events = []
465 self.sent_log_event = []
466
467 def log(self, log_event):
468 self.pending_log_events.append(log_event)
469
470 def flush_events(self):
471 self.sent_log_event.extend(self.pending_log_events)
472 self.pending_log_events.clear()
473
474 def get_sent_events(self):
475 return self.sent_log_event + self.pending_log_events
476
Zhuoyao Zhang53359552024-09-16 23:58:11 +0000477
478if __name__ == '__main__':
479 unittest.main()