diff options
Diffstat (limited to 'tests/test_git_trace2_event_log.py')
-rw-r--r-- | tests/test_git_trace2_event_log.py | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/tests/test_git_trace2_event_log.py b/tests/test_git_trace2_event_log.py new file mode 100644 index 00000000..89dcfb92 --- /dev/null +++ b/tests/test_git_trace2_event_log.py | |||
@@ -0,0 +1,329 @@ | |||
1 | # Copyright (C) 2020 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 the git_trace2_event_log.py module.""" | ||
16 | |||
17 | import json | ||
18 | import os | ||
19 | import tempfile | ||
20 | import unittest | ||
21 | from unittest import mock | ||
22 | |||
23 | import git_trace2_event_log | ||
24 | |||
25 | |||
26 | class EventLogTestCase(unittest.TestCase): | ||
27 | """TestCase for the EventLog module.""" | ||
28 | |||
29 | PARENT_SID_KEY = 'GIT_TRACE2_PARENT_SID' | ||
30 | PARENT_SID_VALUE = 'parent_sid' | ||
31 | SELF_SID_REGEX = r'repo-\d+T\d+Z-.*' | ||
32 | FULL_SID_REGEX = r'^%s/%s' % (PARENT_SID_VALUE, SELF_SID_REGEX) | ||
33 | |||
34 | def setUp(self): | ||
35 | """Load the event_log module every time.""" | ||
36 | self._event_log_module = None | ||
37 | # By default we initialize with the expected case where | ||
38 | # repo launches us (so GIT_TRACE2_PARENT_SID is set). | ||
39 | env = { | ||
40 | self.PARENT_SID_KEY: self.PARENT_SID_VALUE, | ||
41 | } | ||
42 | self._event_log_module = git_trace2_event_log.EventLog(env=env) | ||
43 | self._log_data = None | ||
44 | |||
45 | def verifyCommonKeys(self, log_entry, expected_event_name=None, full_sid=True): | ||
46 | """Helper function to verify common event log keys.""" | ||
47 | self.assertIn('event', log_entry) | ||
48 | self.assertIn('sid', log_entry) | ||
49 | self.assertIn('thread', log_entry) | ||
50 | self.assertIn('time', log_entry) | ||
51 | |||
52 | # Do basic data format validation. | ||
53 | if expected_event_name: | ||
54 | self.assertEqual(expected_event_name, log_entry['event']) | ||
55 | if full_sid: | ||
56 | self.assertRegex(log_entry['sid'], self.FULL_SID_REGEX) | ||
57 | else: | ||
58 | self.assertRegex(log_entry['sid'], self.SELF_SID_REGEX) | ||
59 | self.assertRegex(log_entry['time'], r'^\d+-\d+-\d+T\d+:\d+:\d+\.\d+Z$') | ||
60 | |||
61 | def readLog(self, log_path): | ||
62 | """Helper function to read log data into a list.""" | ||
63 | log_data = [] | ||
64 | with open(log_path, mode='rb') as f: | ||
65 | for line in f: | ||
66 | log_data.append(json.loads(line)) | ||
67 | return log_data | ||
68 | |||
69 | def remove_prefix(self, s, prefix): | ||
70 | """Return a copy string after removing |prefix| from |s|, if present or the original string.""" | ||
71 | if s.startswith(prefix): | ||
72 | return s[len(prefix):] | ||
73 | else: | ||
74 | return s | ||
75 | |||
76 | def test_initial_state_with_parent_sid(self): | ||
77 | """Test initial state when 'GIT_TRACE2_PARENT_SID' is set by parent.""" | ||
78 | self.assertRegex(self._event_log_module.full_sid, self.FULL_SID_REGEX) | ||
79 | |||
80 | def test_initial_state_no_parent_sid(self): | ||
81 | """Test initial state when 'GIT_TRACE2_PARENT_SID' is not set.""" | ||
82 | # Setup an empty environment dict (no parent sid). | ||
83 | self._event_log_module = git_trace2_event_log.EventLog(env={}) | ||
84 | self.assertRegex(self._event_log_module.full_sid, self.SELF_SID_REGEX) | ||
85 | |||
86 | def test_version_event(self): | ||
87 | """Test 'version' event data is valid. | ||
88 | |||
89 | Verify that the 'version' event is written even when no other | ||
90 | events are addded. | ||
91 | |||
92 | Expected event log: | ||
93 | <version event> | ||
94 | """ | ||
95 | with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir: | ||
96 | log_path = self._event_log_module.Write(path=tempdir) | ||
97 | self._log_data = self.readLog(log_path) | ||
98 | |||
99 | # A log with no added events should only have the version entry. | ||
100 | self.assertEqual(len(self._log_data), 1) | ||
101 | version_event = self._log_data[0] | ||
102 | self.verifyCommonKeys(version_event, expected_event_name='version') | ||
103 | # Check for 'version' event specific fields. | ||
104 | self.assertIn('evt', version_event) | ||
105 | self.assertIn('exe', version_event) | ||
106 | # Verify "evt" version field is a string. | ||
107 | self.assertIsInstance(version_event['evt'], str) | ||
108 | |||
109 | def test_start_event(self): | ||
110 | """Test and validate 'start' event data is valid. | ||
111 | |||
112 | Expected event log: | ||
113 | <version event> | ||
114 | <start event> | ||
115 | """ | ||
116 | self._event_log_module.StartEvent() | ||
117 | with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir: | ||
118 | log_path = self._event_log_module.Write(path=tempdir) | ||
119 | self._log_data = self.readLog(log_path) | ||
120 | |||
121 | self.assertEqual(len(self._log_data), 2) | ||
122 | start_event = self._log_data[1] | ||
123 | self.verifyCommonKeys(self._log_data[0], expected_event_name='version') | ||
124 | self.verifyCommonKeys(start_event, expected_event_name='start') | ||
125 | # Check for 'start' event specific fields. | ||
126 | self.assertIn('argv', start_event) | ||
127 | self.assertTrue(isinstance(start_event['argv'], list)) | ||
128 | |||
129 | def test_exit_event_result_none(self): | ||
130 | """Test 'exit' event data is valid when result is None. | ||
131 | |||
132 | We expect None result to be converted to 0 in the exit event data. | ||
133 | |||
134 | Expected event log: | ||
135 | <version event> | ||
136 | <exit event> | ||
137 | """ | ||
138 | self._event_log_module.ExitEvent(None) | ||
139 | with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir: | ||
140 | log_path = self._event_log_module.Write(path=tempdir) | ||
141 | self._log_data = self.readLog(log_path) | ||
142 | |||
143 | self.assertEqual(len(self._log_data), 2) | ||
144 | exit_event = self._log_data[1] | ||
145 | self.verifyCommonKeys(self._log_data[0], expected_event_name='version') | ||
146 | self.verifyCommonKeys(exit_event, expected_event_name='exit') | ||
147 | # Check for 'exit' event specific fields. | ||
148 | self.assertIn('code', exit_event) | ||
149 | # 'None' result should convert to 0 (successful) return code. | ||
150 | self.assertEqual(exit_event['code'], 0) | ||
151 | |||
152 | def test_exit_event_result_integer(self): | ||
153 | """Test 'exit' event data is valid when result is an integer. | ||
154 | |||
155 | Expected event log: | ||
156 | <version event> | ||
157 | <exit event> | ||
158 | """ | ||
159 | self._event_log_module.ExitEvent(2) | ||
160 | with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir: | ||
161 | log_path = self._event_log_module.Write(path=tempdir) | ||
162 | self._log_data = self.readLog(log_path) | ||
163 | |||
164 | self.assertEqual(len(self._log_data), 2) | ||
165 | exit_event = self._log_data[1] | ||
166 | self.verifyCommonKeys(self._log_data[0], expected_event_name='version') | ||
167 | self.verifyCommonKeys(exit_event, expected_event_name='exit') | ||
168 | # Check for 'exit' event specific fields. | ||
169 | self.assertIn('code', exit_event) | ||
170 | self.assertEqual(exit_event['code'], 2) | ||
171 | |||
172 | def test_command_event(self): | ||
173 | """Test and validate 'command' event data is valid. | ||
174 | |||
175 | Expected event log: | ||
176 | <version event> | ||
177 | <command event> | ||
178 | """ | ||
179 | name = 'repo' | ||
180 | subcommands = ['init' 'this'] | ||
181 | self._event_log_module.CommandEvent(name='repo', subcommands=subcommands) | ||
182 | with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir: | ||
183 | log_path = self._event_log_module.Write(path=tempdir) | ||
184 | self._log_data = self.readLog(log_path) | ||
185 | |||
186 | self.assertEqual(len(self._log_data), 2) | ||
187 | command_event = self._log_data[1] | ||
188 | self.verifyCommonKeys(self._log_data[0], expected_event_name='version') | ||
189 | self.verifyCommonKeys(command_event, expected_event_name='command') | ||
190 | # Check for 'command' event specific fields. | ||
191 | self.assertIn('name', command_event) | ||
192 | self.assertIn('subcommands', command_event) | ||
193 | self.assertEqual(command_event['name'], name) | ||
194 | self.assertEqual(command_event['subcommands'], subcommands) | ||
195 | |||
196 | def test_def_params_event_repo_config(self): | ||
197 | """Test 'def_params' event data outputs only repo config keys. | ||
198 | |||
199 | Expected event log: | ||
200 | <version event> | ||
201 | <def_param event> | ||
202 | <def_param event> | ||
203 | """ | ||
204 | config = { | ||
205 | 'git.foo': 'bar', | ||
206 | 'repo.partialclone': 'true', | ||
207 | 'repo.partialclonefilter': 'blob:none', | ||
208 | } | ||
209 | self._event_log_module.DefParamRepoEvents(config) | ||
210 | |||
211 | with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir: | ||
212 | log_path = self._event_log_module.Write(path=tempdir) | ||
213 | self._log_data = self.readLog(log_path) | ||
214 | |||
215 | self.assertEqual(len(self._log_data), 3) | ||
216 | def_param_events = self._log_data[1:] | ||
217 | self.verifyCommonKeys(self._log_data[0], expected_event_name='version') | ||
218 | |||
219 | for event in def_param_events: | ||
220 | self.verifyCommonKeys(event, expected_event_name='def_param') | ||
221 | # Check for 'def_param' event specific fields. | ||
222 | self.assertIn('param', event) | ||
223 | self.assertIn('value', event) | ||
224 | self.assertTrue(event['param'].startswith('repo.')) | ||
225 | |||
226 | def test_def_params_event_no_repo_config(self): | ||
227 | """Test 'def_params' event data won't output non-repo config keys. | ||
228 | |||
229 | Expected event log: | ||
230 | <version event> | ||
231 | """ | ||
232 | config = { | ||
233 | 'git.foo': 'bar', | ||
234 | 'git.core.foo2': 'baz', | ||
235 | } | ||
236 | self._event_log_module.DefParamRepoEvents(config) | ||
237 | |||
238 | with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir: | ||
239 | log_path = self._event_log_module.Write(path=tempdir) | ||
240 | self._log_data = self.readLog(log_path) | ||
241 | |||
242 | self.assertEqual(len(self._log_data), 1) | ||
243 | self.verifyCommonKeys(self._log_data[0], expected_event_name='version') | ||
244 | |||
245 | def test_data_event_config(self): | ||
246 | """Test 'data' event data outputs all config keys. | ||
247 | |||
248 | Expected event log: | ||
249 | <version event> | ||
250 | <data event> | ||
251 | <data event> | ||
252 | """ | ||
253 | config = { | ||
254 | 'git.foo': 'bar', | ||
255 | 'repo.partialclone': 'false', | ||
256 | 'repo.syncstate.superproject.hassuperprojecttag': 'true', | ||
257 | 'repo.syncstate.superproject.sys.argv': ['--', 'sync', 'protobuf'], | ||
258 | } | ||
259 | prefix_value = 'prefix' | ||
260 | self._event_log_module.LogDataConfigEvents(config, prefix_value) | ||
261 | |||
262 | with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir: | ||
263 | log_path = self._event_log_module.Write(path=tempdir) | ||
264 | self._log_data = self.readLog(log_path) | ||
265 | |||
266 | self.assertEqual(len(self._log_data), 5) | ||
267 | data_events = self._log_data[1:] | ||
268 | self.verifyCommonKeys(self._log_data[0], expected_event_name='version') | ||
269 | |||
270 | for event in data_events: | ||
271 | self.verifyCommonKeys(event) | ||
272 | # Check for 'data' event specific fields. | ||
273 | self.assertIn('key', event) | ||
274 | self.assertIn('value', event) | ||
275 | key = event['key'] | ||
276 | key = self.remove_prefix(key, f'{prefix_value}/') | ||
277 | value = event['value'] | ||
278 | self.assertEqual(self._event_log_module.GetDataEventName(value), event['event']) | ||
279 | self.assertTrue(key in config and value == config[key]) | ||
280 | |||
281 | def test_error_event(self): | ||
282 | """Test and validate 'error' event data is valid. | ||
283 | |||
284 | Expected event log: | ||
285 | <version event> | ||
286 | <error event> | ||
287 | """ | ||
288 | msg = 'invalid option: --cahced' | ||
289 | fmt = 'invalid option: %s' | ||
290 | self._event_log_module.ErrorEvent(msg, fmt) | ||
291 | with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir: | ||
292 | log_path = self._event_log_module.Write(path=tempdir) | ||
293 | self._log_data = self.readLog(log_path) | ||
294 | |||
295 | self.assertEqual(len(self._log_data), 2) | ||
296 | error_event = self._log_data[1] | ||
297 | self.verifyCommonKeys(self._log_data[0], expected_event_name='version') | ||
298 | self.verifyCommonKeys(error_event, expected_event_name='error') | ||
299 | # Check for 'error' event specific fields. | ||
300 | self.assertIn('msg', error_event) | ||
301 | self.assertIn('fmt', error_event) | ||
302 | self.assertEqual(error_event['msg'], msg) | ||
303 | self.assertEqual(error_event['fmt'], fmt) | ||
304 | |||
305 | def test_write_with_filename(self): | ||
306 | """Test Write() with a path to a file exits with None.""" | ||
307 | self.assertIsNone(self._event_log_module.Write(path='path/to/file')) | ||
308 | |||
309 | def test_write_with_git_config(self): | ||
310 | """Test Write() uses the git config path when 'git config' call succeeds.""" | ||
311 | with tempfile.TemporaryDirectory(prefix='event_log_tests') as tempdir: | ||
312 | with mock.patch.object(self._event_log_module, | ||
313 | '_GetEventTargetPath', return_value=tempdir): | ||
314 | self.assertEqual(os.path.dirname(self._event_log_module.Write()), tempdir) | ||
315 | |||
316 | def test_write_no_git_config(self): | ||
317 | """Test Write() with no git config variable present exits with None.""" | ||
318 | with mock.patch.object(self._event_log_module, | ||
319 | '_GetEventTargetPath', return_value=None): | ||
320 | self.assertIsNone(self._event_log_module.Write()) | ||
321 | |||
322 | def test_write_non_string(self): | ||
323 | """Test Write() with non-string type for |path| throws TypeError.""" | ||
324 | with self.assertRaises(TypeError): | ||
325 | self._event_log_module.Write(path=1234) | ||
326 | |||
327 | |||
328 | if __name__ == '__main__': | ||
329 | unittest.main() | ||