1# 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 ToolEventLogger.""" 16 17import datetime 18import logging 19import unittest 20from unittest import mock 21 22from atest.metrics import clearcut_client 23from proto import tool_event_pb2 24from tool_event_logger import tool_event_logger 25 26TEST_INVOCATION_ID = 'test_invocation_id' 27TEST_USER_NAME = 'test_user' 28TEST_HOST_NAME = 'test_host_name' 29TEST_TOOL_TAG = 'test_tool' 30TEST_SOURCE_ROOT = 'test_source_root' 31TEST_PLATFORM_VERSION = 'test_platform_version' 32TEST_PYTHON_VERSION = 'test_python_version' 33TEST_EVENT_TIMESTAMP = datetime.datetime.now() 34 35 36class ToolEventLoggerTest(unittest.TestCase): 37 38 def setUp(self): 39 super().setUp() 40 self.clearcut_client = FakeClearcutClient() 41 self.logger = tool_event_logger.ToolEventLogger( 42 TEST_TOOL_TAG, 43 TEST_INVOCATION_ID, 44 TEST_USER_NAME, 45 TEST_HOST_NAME, 46 TEST_SOURCE_ROOT, 47 TEST_PLATFORM_VERSION, 48 TEST_PYTHON_VERSION, 49 client=self.clearcut_client, 50 ) 51 52 def test_log_event_timestamp(self): 53 with self.logger: 54 self.logger.log_invocation_started( 55 datetime.datetime.fromtimestamp(100.101), 'test_command' 56 ) 57 58 self.assertEqual( 59 self.clearcut_client.get_last_sent_event().event_time_ms, 100101 60 ) 61 62 def test_log_event_basic_information(self): 63 with self.logger: 64 self.logger.log_invocation_started(TEST_EVENT_TIMESTAMP, 'test_command') 65 66 sent_event = self.clearcut_client.get_last_sent_event() 67 log_event = tool_event_pb2.ToolEvent.FromString(sent_event.source_extension) 68 self.assertEqual(log_event.invocation_id, TEST_INVOCATION_ID) 69 self.assertEqual(log_event.user_name, TEST_USER_NAME) 70 self.assertEqual(log_event.host_name, TEST_HOST_NAME) 71 self.assertEqual(log_event.tool_tag, TEST_TOOL_TAG) 72 self.assertEqual(log_event.source_root, TEST_SOURCE_ROOT) 73 74 def test_log_invocation_started(self): 75 expected_invocation_started = tool_event_pb2.ToolEvent.InvocationStarted( 76 command_args='test_command', 77 os=TEST_PLATFORM_VERSION + ':' + TEST_PYTHON_VERSION, 78 ) 79 80 with self.logger: 81 self.logger.log_invocation_started(TEST_EVENT_TIMESTAMP, 'test_command') 82 83 self.assertEqual(self.clearcut_client.get_number_of_sent_events(), 1) 84 sent_event = self.clearcut_client.get_last_sent_event() 85 self.assertEqual( 86 expected_invocation_started, 87 tool_event_pb2.ToolEvent.FromString( 88 sent_event.source_extension 89 ).invocation_started, 90 ) 91 92 def test_log_invocation_stopped(self): 93 expected_invocation_stopped = tool_event_pb2.ToolEvent.InvocationStopped( 94 exit_code=0, 95 exit_log='exit_log', 96 ) 97 98 with self.logger: 99 self.logger.log_invocation_stopped(TEST_EVENT_TIMESTAMP, 0, 'exit_log') 100 101 self.assertEqual(self.clearcut_client.get_number_of_sent_events(), 1) 102 sent_event = self.clearcut_client.get_last_sent_event() 103 self.assertEqual( 104 expected_invocation_stopped, 105 tool_event_pb2.ToolEvent.FromString( 106 sent_event.source_extension 107 ).invocation_stopped, 108 ) 109 110 def test_log_multiple_events(self): 111 with self.logger: 112 self.logger.log_invocation_started(TEST_EVENT_TIMESTAMP, 'test_command') 113 self.logger.log_invocation_stopped(TEST_EVENT_TIMESTAMP, 0, 'exit_log') 114 115 self.assertEqual(self.clearcut_client.get_number_of_sent_events(), 2) 116 117 118class MainTest(unittest.TestCase): 119 120 REQUIRED_ARGS = [ 121 '', 122 '--tool_tag', 123 'test_tool', 124 '--start_timestamp', 125 '1', 126 '--end_timestamp', 127 '2', 128 '--exit_code', 129 '0', 130 ] 131 132 def test_log_and_exit_with_missing_required_args(self): 133 with self.assertLogs() as logs: 134 with self.assertRaises(SystemExit) as ex: 135 tool_event_logger.main(['', '--tool_tag', 'test_tool']) 136 137 with self.subTest('Verify exception code'): 138 self.assertEqual(ex.exception.code, 2) 139 140 with self.subTest('Verify log messages'): 141 self.assertIn( 142 'the following arguments are required', 143 '\n'.join(logs.output), 144 ) 145 146 def test_log_and_exit_with_invalid_args(self): 147 with self.assertLogs() as logs: 148 with self.assertRaises(SystemExit) as ex: 149 tool_event_logger.main(['', '--start_timestamp', 'test']) 150 151 with self.subTest('Verify exception code'): 152 self.assertEqual(ex.exception.code, 2) 153 154 with self.subTest('Verify log messages'): 155 self.assertIn( 156 '--start_timestamp: invalid', 157 '\n'.join(logs.output), 158 ) 159 160 def test_log_and_exit_with_dry_run(self): 161 with self.assertLogs(level=logging.DEBUG) as logs: 162 tool_event_logger.main(self.REQUIRED_ARGS + ['--dry_run']) 163 164 with self.subTest('Verify log messages'): 165 self.assertIn('dry run', '\n'.join(logs.output)) 166 167 @mock.patch.object(clearcut_client, 'Clearcut') 168 def test_log_and_exit_with_unexpected_exception(self, mock_cc): 169 mock_cc.return_value = FakeClearcutClient(raise_log_exception=True) 170 171 with self.assertLogs() as logs: 172 with self.assertRaises(Exception) as ex: 173 tool_event_logger.main(self.REQUIRED_ARGS) 174 175 with self.subTest('Verify log messages'): 176 self.assertIn('unexpected error', '\n'.join(logs.output)) 177 178 @mock.patch.object(clearcut_client, 'Clearcut') 179 def test_success(self, mock_cc): 180 mock_clear_cut_client = FakeClearcutClient() 181 mock_cc.return_value = mock_clear_cut_client 182 183 tool_event_logger.main(self.REQUIRED_ARGS) 184 185 self.assertEqual(mock_clear_cut_client.get_number_of_sent_events(), 2) 186 187 188class FakeClearcutClient: 189 190 def __init__(self, raise_log_exception=False): 191 self.pending_log_events = [] 192 self.sent_log_events = [] 193 self.raise_log_exception = raise_log_exception 194 195 def log(self, log_event): 196 if self.raise_log_exception: 197 raise Exception('unknown exception') 198 self.pending_log_events.append(log_event) 199 200 def flush_events(self): 201 self.sent_log_events.extend(self.pending_log_events) 202 self.pending_log_events.clear() 203 204 def get_number_of_sent_events(self): 205 return len(self.sent_log_events) 206 207 def get_last_sent_event(self): 208 return self.sent_log_events[-1] 209 210 211if __name__ == '__main__': 212 unittest.main() 213