blob: 3565d538cb8f00abf461ef9092d00f9b00c0489b [file] [log] [blame]
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -07001#!/usr/bin/python
2
3"""Unit tests for the perf_uploader.py module.
4
5"""
6
Po-Hsien Wang91565752019-06-13 14:01:02 -07007import json
8import os
9import tempfile
10import unittest
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -070011
12import common
13from autotest_lib.tko import models as tko_models
14from autotest_lib.tko.perf_upload import perf_uploader
15
16
17class test_aggregate_iterations(unittest.TestCase):
18 """Tests for the aggregate_iterations function."""
19
20 _PERF_ITERATION_DATA = {
21 '1': [
22 {
23 'description': 'metric1',
24 'value': 1,
25 'stddev': 0.0,
26 'units': 'units1',
Fang Deng7f24f0b2013-11-12 11:22:16 -080027 'higher_is_better': True,
Quinten Yearsleyad38c6a2015-04-13 15:29:25 -070028 'graph': None
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -070029 },
30 {
31 'description': 'metric2',
32 'value': 10,
33 'stddev': 0.0,
34 'units': 'units2',
Fang Deng7f24f0b2013-11-12 11:22:16 -080035 'higher_is_better': True,
Puthikorn Voravootivatad2c1e62014-06-10 18:04:03 -070036 'graph': 'graph1',
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -070037 },
38 {
Puthikorn Voravootivatad2c1e62014-06-10 18:04:03 -070039 'description': 'metric2',
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -070040 'value': 100,
41 'stddev': 1.7,
42 'units': 'units3',
Fang Deng7f24f0b2013-11-12 11:22:16 -080043 'higher_is_better': False,
Puthikorn Voravootivatad2c1e62014-06-10 18:04:03 -070044 'graph': 'graph2',
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -070045 }
46 ],
47 '2': [
48 {
49 'description': 'metric1',
50 'value': 2,
51 'stddev': 0.0,
52 'units': 'units1',
Fang Deng7f24f0b2013-11-12 11:22:16 -080053 'higher_is_better': True,
Quinten Yearsleyad38c6a2015-04-13 15:29:25 -070054 'graph': None,
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -070055 },
56 {
57 'description': 'metric2',
58 'value': 20,
59 'stddev': 0.0,
60 'units': 'units2',
Fang Deng7f24f0b2013-11-12 11:22:16 -080061 'higher_is_better': True,
Puthikorn Voravootivatad2c1e62014-06-10 18:04:03 -070062 'graph': 'graph1',
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -070063 },
64 {
Puthikorn Voravootivatad2c1e62014-06-10 18:04:03 -070065 'description': 'metric2',
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -070066 'value': 200,
67 'stddev': 21.2,
68 'units': 'units3',
Fang Deng7f24f0b2013-11-12 11:22:16 -080069 'higher_is_better': False,
Puthikorn Voravootivatad2c1e62014-06-10 18:04:03 -070070 'graph': 'graph2',
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -070071 }
72 ],
73 }
74
75
76 def setUp(self):
77 """Sets up for each test case."""
78 self._perf_values = []
79 for iter_num, iter_data in self._PERF_ITERATION_DATA.iteritems():
80 self._perf_values.append(
81 tko_models.perf_value_iteration(iter_num, iter_data))
82
83
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -070084
85class test_json_config_file_sanity(unittest.TestCase):
86 """Sanity tests for the JSON-formatted presentation config file."""
87
Dan Shib9fec882016-01-05 15:18:30 -080088 def test_parse_json(self):
89 """Verifies _parse_config_file function."""
90 perf_uploader._parse_config_file(
91 perf_uploader._PRESENTATION_CONFIG_FILE)
92
Po-Hsien Wang91565752019-06-13 14:01:02 -070093 def test_proper_config(self):
94 """Verifies configs have either autotest_name or autotest_regex."""
95 json_obj = []
96 try:
97 with open(perf_uploader._PRESENTATION_CONFIG_FILE, 'r') as fp:
98 json_obj = json.load(fp)
99 except:
100 self.fail('Presentation config file could not be parsed as JSON.')
101
102 for entry in json_obj:
103 if 'autotest_name' not in entry and 'autotest_regex' not in entry:
104 self.fail('Missing autotest_name or autotest_regex field for '
105 'test %s.' % entry)
106
Dan Shib9fec882016-01-05 15:18:30 -0800107
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700108 def test_proper_json(self):
109 """Verifies the file can be parsed as proper JSON."""
110 try:
111 with open(perf_uploader._PRESENTATION_CONFIG_FILE, 'r') as fp:
112 json.load(fp)
113 except:
114 self.fail('Presentation config file could not be parsed as JSON.')
115
116
Fang Deng947502e2014-05-07 11:59:07 -0700117 def test_required_master_name(self):
118 """Verifies that master name must be specified."""
119 json_obj = []
120 try:
121 with open(perf_uploader._PRESENTATION_CONFIG_FILE, 'r') as fp:
122 json_obj = json.load(fp)
123 except:
124 self.fail('Presentation config file could not be parsed as JSON.')
125
126 for entry in json_obj:
127 if not 'master_name' in entry:
128 self.fail('Missing master field for test %s.' %
129 entry['autotest_name'])
130
131
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700132class test_gather_presentation_info(unittest.TestCase):
133 """Tests for the gather_presentation_info function."""
134
135 _PRESENT_INFO = {
136 'test_name': {
137 'master_name': 'new_master_name',
138 'dashboard_test_name': 'new_test_name',
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700139 }
140 }
141
Fang Deng947502e2014-05-07 11:59:07 -0700142 _PRESENT_INFO_MISSING_MASTER = {
143 'test_name': {
144 'dashboard_test_name': 'new_test_name',
145 }
146 }
147
Po-Hsien Wangccaa0592018-11-12 20:57:20 -0800148 _PRESENT_INFO_REGEX = {
149 'test_name.*': {
150 'master_name': 'new_master_name',
151 'dashboard_test_name': 'new_test_name',
152 }
153 }
154
Po-Hsien Wang91565752019-06-13 14:01:02 -0700155 _PRESENT_INFO_COLLISION = {
156 'test_name.*': {
157 'master_name': 'new_master_name',
158 'dashboard_test_name': 'new_test_name',
159 },
160 'test_name-test.*': {
161 'master_name': 'new_master_name',
162 'dashboard_test_name': 'new_test_name',
163 },
164 }
165
166 def test_test_selection_collision(self):
167 """Verifies error when multiple entry refers to the same test."""
168 try:
169 result = perf_uploader._gather_presentation_info(
170 self._PRESENT_INFO_COLLISION, 'test_name-test-23')
171 self.fail('PerfUploadingError is expected if more than one entry '
172 'refer to the same test.')
173 except perf_uploader.PerfUploadingError:
174 return
175
Po-Hsien Wangccaa0592018-11-12 20:57:20 -0800176 def test_test_name_regex_specified(self):
177 """Verifies gathers presentation info for regex search correctly"""
178 for test_name in ['test_name.arm.7.1', 'test_name.x86.7.1']:
179 result = perf_uploader._gather_presentation_info(
180 self._PRESENT_INFO, 'test_name_P')
181 self.assertTrue(
182 all([key in result for key in
183 ['test_name', 'master_name']]),
184 msg='Unexpected keys in resulting dictionary: %s' % result)
185 self.assertEqual(result['master_name'], 'new_master_name',
186 msg='Unexpected "master_name" value: %s' %
187 result['master_name'])
188 self.assertEqual(result['test_name'], 'new_test_name',
189 msg='Unexpected "test_name" value: %s' %
190 result['test_name'])
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700191
192 def test_test_name_specified(self):
193 """Verifies gathers presentation info correctly."""
194 result = perf_uploader._gather_presentation_info(
195 self._PRESENT_INFO, 'test_name')
196 self.assertTrue(
197 all([key in result for key in
Fang Deng7f24f0b2013-11-12 11:22:16 -0800198 ['test_name', 'master_name']]),
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700199 msg='Unexpected keys in resulting dictionary: %s' % result)
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700200 self.assertEqual(result['master_name'], 'new_master_name',
201 msg='Unexpected "master_name" value: %s' %
202 result['master_name'])
203 self.assertEqual(result['test_name'], 'new_test_name',
204 msg='Unexpected "test_name" value: %s' %
205 result['test_name'])
206
207
208 def test_test_name_not_specified(self):
Fang Deng947502e2014-05-07 11:59:07 -0700209 """Verifies exception raised if test is not there."""
210 self.assertRaises(
211 perf_uploader.PerfUploadingError,
212 perf_uploader._gather_presentation_info,
213 self._PRESENT_INFO, 'other_test_name')
214
215
216 def test_master_not_specified(self):
217 """Verifies exception raised if master is not there."""
218 self.assertRaises(
219 perf_uploader.PerfUploadingError,
220 perf_uploader._gather_presentation_info,
221 self._PRESENT_INFO_MISSING_MASTER, 'test_name')
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700222
223
Po-Hsien Wang91565752019-06-13 14:01:02 -0700224class test_parse_and_gather_presentation(unittest.TestCase):
225 """Tests for _parse_config_file and then_gather_presentation_info."""
226 _AUTOTEST_NAME_CONFIG = """[{
227 "autotest_name": "test.test.VM",
228 "master_name": "ChromeOSPerf"
229 }]"""
230
231 _AUTOTEST_REGEX_CONFIG = r"""[{
232 "autotest_regex": "test\\.test\\.VM.*",
233 "master_name": "ChromeOSPerf"
234 }]"""
235
236 def setUp(self):
237 _, self._temp_path = tempfile.mkstemp()
238
239 def tearDown(self):
240 os.remove(self._temp_path)
241
242 def test_autotest_name_is_matched(self):
243 """Verifies that autotest name is matched to the test properly."""
244 with open(self._temp_path, 'w') as f:
245 f.write(self._AUTOTEST_NAME_CONFIG)
246 config = perf_uploader._parse_config_file(self._temp_path)
247 test_name = 'test.test.VM'
248 result = perf_uploader._gather_presentation_info(config, test_name)
249 self.assertEqual(result, {
250 'test_name': test_name,
251 'master_name': 'ChromeOSPerf'
252 })
253
Po-Hsien Wangb7d6b022019-06-28 15:07:37 -0700254 def test_autotest_name_is_exact_matched(self):
255 """Verifies that autotest name is exact matched to the test properly."""
256 with open(self._temp_path, 'w') as f:
257 f.write(self._AUTOTEST_NAME_CONFIG)
258 config = perf_uploader._parse_config_file(self._temp_path)
259 test_name = 'test.test.VM.test'
260 try:
261 perf_uploader._gather_presentation_info(config, test_name)
262 self.fail(
263 'PerfUploadingError is expected for %s. autotest_name should '
264 'be exactly matched.' % test_name)
265 except perf_uploader.PerfUploadingError:
266 return
267
Po-Hsien Wang91565752019-06-13 14:01:02 -0700268 def test_autotest_name_is_escaped(self):
269 """Verifies that autotest name is escaped properly."""
270 with open(self._temp_path, 'w') as f:
271 f.write(self._AUTOTEST_NAME_CONFIG)
272 config = perf_uploader._parse_config_file(self._temp_path)
273 try:
274 test_name = 'test.testkVM'
275 result = perf_uploader._gather_presentation_info(
276 config, test_name)
277 self.fail(
278 'PerfUploadingError is expected for %s. autotest_name should '
279 'be escaped' % test_name)
280 except perf_uploader.PerfUploadingError:
281 return
282
283 def test_autotest_regex_is_matched(self):
284 """Verifies that autotest regex is matched to the test properly."""
285 with open(self._temp_path, 'w') as f:
286 f.write(self._AUTOTEST_REGEX_CONFIG)
287 config = perf_uploader._parse_config_file(self._temp_path)
288 for test_name in ['test.test.VM1', 'test.test.VMTest']:
289 result = perf_uploader._gather_presentation_info(config, test_name)
290 self.assertEqual(result, {
291 'test_name': test_name,
292 'master_name': 'ChromeOSPerf'
293 })
294
295 def test_autotest_regex_is_not_matched(self):
296 """Verifies that autotest regex is matched to the test properly."""
297 with open(self._temp_path, 'w') as f:
298 f.write(self._AUTOTEST_REGEX_CONFIG)
299 config = perf_uploader._parse_config_file(self._temp_path)
300 for test_name in ['testktest.VM', 'test.testkVM', 'test.test\VM']:
301 try:
302 result = perf_uploader._gather_presentation_info(
303 config, test_name)
304 self.fail('PerfUploadingError is expected for %s' % test_name)
305 except perf_uploader.PerfUploadingError:
306 return
307
Quinten Yearsley5a66aea2015-04-14 12:40:25 -0700308class test_get_id_from_version(unittest.TestCase):
309 """Tests for the _get_id_from_version function."""
310
311 def test_correctly_formatted_versions(self):
312 """Verifies that the expected ID is returned when input is OK."""
313 chrome_version = '27.0.1452.2'
314 cros_version = '27.3906.0.0'
315 # 1452.2 + 3906.0.0
316 # --> 01452 + 002 + 03906 + 000 + 00
317 # --> 14520020390600000
318 self.assertEqual(
319 14520020390600000,
320 perf_uploader._get_id_from_version(
321 chrome_version, cros_version))
322
323 chrome_version = '25.10.1000.0'
324 cros_version = '25.1200.0.0'
325 # 1000.0 + 1200.0.0
326 # --> 01000 + 000 + 01200 + 000 + 00
327 # --> 10000000120000000
328 self.assertEqual(
329 10000000120000000,
330 perf_uploader._get_id_from_version(
331 chrome_version, cros_version))
332
333 def test_returns_none_when_given_invalid_input(self):
334 """Checks the return value when invalid input is given."""
335 chrome_version = '27.0'
336 cros_version = '27.3906.0.0'
337 self.assertIsNone(perf_uploader._get_id_from_version(
338 chrome_version, cros_version))
339
340
341class test_get_version_numbers(unittest.TestCase):
342 """Tests for the _get_version_numbers function."""
343
344 def test_with_valid_versions(self):
345 """Checks the version numbers used when data is formatted as expected."""
346 self.assertEqual(
347 ('34.5678.9.0', '34.5.678.9'),
348 perf_uploader._get_version_numbers(
349 {
350 'CHROME_VERSION': '34.5.678.9',
351 'CHROMEOS_RELEASE_VERSION': '5678.9.0',
352 }))
353
354 def test_with_missing_version_raises_error(self):
355 """Checks that an error is raised when a version is missing."""
356 with self.assertRaises(perf_uploader.PerfUploadingError):
357 perf_uploader._get_version_numbers(
358 {
359 'CHROMEOS_RELEASE_VERSION': '5678.9.0',
360 })
361
362 def test_with_unexpected_version_format_raises_error(self):
363 """Checks that an error is raised when there's a rN suffix."""
364 with self.assertRaises(perf_uploader.PerfUploadingError):
365 perf_uploader._get_version_numbers(
366 {
367 'CHROME_VERSION': '34.5.678.9',
368 'CHROMEOS_RELEASE_VERSION': '5678.9.0r1',
369 })
370
Keith Haddow60ddbb32016-05-23 10:34:54 -0700371 def test_with_valid_release_milestone(self):
372 """Checks the version numbers used when data is formatted as expected."""
373 self.assertEqual(
374 ('54.5678.9.0', '34.5.678.9'),
375 perf_uploader._get_version_numbers(
376 {
377 'CHROME_VERSION': '34.5.678.9',
378 'CHROMEOS_RELEASE_VERSION': '5678.9.0',
379 'CHROMEOS_RELEASE_CHROME_MILESTONE': '54',
380 }))
381
Quinten Yearsley5a66aea2015-04-14 12:40:25 -0700382
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700383class test_format_for_upload(unittest.TestCase):
384 """Tests for the format_for_upload function."""
385
386 _PERF_DATA = {
Keith Haddow1e5c7012016-03-09 16:05:37 -0800387 "charts": {
388 "metric1": {
389 "summary": {
390 "improvement_direction": "down",
391 "type": "scalar",
392 "units": "msec",
393 "value": 2.7,
394 }
395 },
396 "metric2": {
397 "summary": {
398 "improvement_direction": "up",
399 "type": "scalar",
400 "units": "frames_per_sec",
401 "value": 101.35,
402 }
403 }
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700404 },
405 }
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700406 _PRESENT_INFO = {
407 'master_name': 'new_master_name',
408 'test_name': 'new_test_name',
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700409 }
410
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700411 def setUp(self):
412 self._perf_data = self._PERF_DATA
413
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700414 def _verify_result_string(self, actual_result, expected_result):
415 """Verifies a JSON string matches the expected result.
416
417 This function compares JSON objects rather than strings, because of
418 possible floating-point values that need to be compared using
419 assertAlmostEqual().
420
421 @param actual_result: The candidate JSON string.
422 @param expected_result: The reference JSON string that the candidate
423 must match.
424
425 """
426 actual = json.loads(actual_result)
427 expected = json.loads(expected_result)
428
Keith Haddow1e5c7012016-03-09 16:05:37 -0800429 def ordered(obj):
430 if isinstance(obj, dict):
431 return sorted((k, ordered(v)) for k, v in obj.items())
432 if isinstance(obj, list):
433 return sorted(ordered(x) for x in obj)
434 else:
435 return obj
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700436 fail_msg = 'Unexpected result string: %s' % actual_result
Keith Haddow1e5c7012016-03-09 16:05:37 -0800437 self.assertEqual(ordered(expected), ordered(actual), msg=fail_msg)
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700438
439
Fang Deng947502e2014-05-07 11:59:07 -0700440 def test_format_for_upload(self):
441 """Verifies format_for_upload generates correct json data."""
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700442 result = perf_uploader._format_for_upload(
Quinten Yearsley5a66aea2015-04-14 12:40:25 -0700443 'platform', '25.1200.0.0', '25.10.1000.0', 'WINKY E2A-F2K-Q35',
harpreet93f7d232019-07-15 15:59:15 -0700444 'test_machine', self._perf_data, self._PRESENT_INFO,
Keith Haddow7a5a7bd2016-02-05 20:24:12 -0800445 '52926644-username/hostname')
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700446 expected_result_string = (
Keith Haddow1e5c7012016-03-09 16:05:37 -0800447 '{"versions": {'
448 '"cros_version": "25.1200.0.0",'
449 '"chrome_version": "25.10.1000.0"'
450 '},'
451 '"point_id": 10000000120000000,'
harpreet93f7d232019-07-15 15:59:15 -0700452 '"bot": "cros-platform",'
Keith Haddow1e5c7012016-03-09 16:05:37 -0800453 '"chart_data": {'
454 '"charts": {'
455 '"metric2": {'
456 '"summary": {'
457 '"units": "frames_per_sec",'
458 '"type": "scalar",'
459 '"value": 101.35,'
460 '"improvement_direction": "up"'
461 '}'
462 '},'
463 '"metric1": {'
464 '"summary": {'
465 '"units": "msec",'
466 '"type": "scalar",'
467 '"value": 2.7,'
468 '"improvement_direction": "down"}'
469 '}'
470 '}'
471 '},'
472 '"master": "new_master_name",'
473 '"supplemental": {'
474 '"hardware_identifier": "WINKY E2A-F2K-Q35",'
475 '"jobname": "52926644-username/hostname",'
476 '"hardware_hostname": "test_machine",'
harpreet93f7d232019-07-15 15:59:15 -0700477 '"default_rev": "r_cros_version"}'
Keith Haddow1e5c7012016-03-09 16:05:37 -0800478 '}')
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700479 self._verify_result_string(result['data'], expected_result_string)
480
481
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700482if __name__ == '__main__':
483 unittest.main()