1#!/usr/bin/python 2 3import re, unittest 4import common 5from autotest_lib.frontend import setup_django_environment 6from autotest_lib.frontend import setup_test_environment 7from autotest_lib.client.common_lib.test_utils import mock 8from django.db import connection 9from autotest_lib.frontend.tko import models, rpc_interface 10 11# this will need to be updated when the view changes for the test to be 12# consistent with reality 13_CREATE_TEST_VIEW = """ 14CREATE VIEW tko_test_view_2 AS 15SELECT tko_tests.test_idx AS test_idx, 16 tko_tests.job_idx AS job_idx, 17 tko_tests.test AS test_name, 18 tko_tests.subdir AS subdir, 19 tko_tests.kernel_idx AS kernel_idx, 20 tko_tests.status AS status_idx, 21 tko_tests.reason AS reason, 22 tko_tests.machine_idx AS machine_idx, 23 tko_tests.invalid AS invalid, 24 tko_tests.invalidates_test_idx AS invalidates_test_idx, 25 tko_tests.started_time AS test_started_time, 26 tko_tests.finished_time AS test_finished_time, 27 tko_jobs.tag AS job_tag, 28 tko_jobs.label AS job_name, 29 tko_jobs.username AS job_owner, 30 tko_jobs.queued_time AS job_queued_time, 31 tko_jobs.started_time AS job_started_time, 32 tko_jobs.finished_time AS job_finished_time, 33 tko_jobs.afe_job_id AS afe_job_id, 34 tko_machines.hostname AS hostname, 35 tko_machines.machine_group AS platform, 36 tko_machines.owner AS machine_owner, 37 tko_kernels.kernel_hash AS kernel_hash, 38 tko_kernels.base AS kernel_base, 39 tko_kernels.printable AS kernel, 40 tko_status.word AS status 41FROM tko_tests 42INNER JOIN tko_jobs ON tko_jobs.job_idx = tko_tests.job_idx 43INNER JOIN tko_machines ON tko_machines.machine_idx = tko_jobs.machine_idx 44INNER JOIN tko_kernels ON tko_kernels.kernel_idx = tko_tests.kernel_idx 45INNER JOIN tko_status ON tko_status.status_idx = tko_tests.status; 46""" 47 48# this will need to be updated if the table schemas change (or removed if we 49# add proper primary keys) 50_CREATE_ITERATION_ATTRIBUTES = """ 51CREATE TABLE "tko_iteration_attributes" ( 52 "test_idx" integer NOT NULL REFERENCES "tko_tests" ("test_idx"), 53 "iteration" integer NOT NULL, 54 "attribute" varchar(90) NOT NULL, 55 "value" varchar(300) NOT NULL 56); 57""" 58 59_CREATE_ITERATION_RESULTS = """ 60CREATE TABLE "tko_iteration_result" ( 61 "test_idx" integer NOT NULL REFERENCES "tko_tests" ("test_idx"), 62 "iteration" integer NOT NULL, 63 "attribute" varchar(90) NOT NULL, 64 "value" numeric(12, 31) NULL 65); 66""" 67 68 69def setup_test_view(): 70 """ 71 Django has no way to actually represent a view; we simply create a model for 72 TestView. This means when we syncdb, Django will create a table for it. 73 So manually remove that table and replace it with a view. 74 """ 75 cursor = connection.cursor() 76 cursor.execute('DROP TABLE tko_test_view_2') 77 cursor.execute(_CREATE_TEST_VIEW) 78 79 80def fix_iteration_tables(): 81 """ 82 Since iteration tables don't have any real primary key, we "fake" one in the 83 Django models. So fix up the generated schema to match the real schema. 84 """ 85 cursor = connection.cursor() 86 cursor.execute('DROP TABLE tko_iteration_attributes') 87 cursor.execute(_CREATE_ITERATION_ATTRIBUTES) 88 cursor.execute('DROP TABLE tko_iteration_result') 89 cursor.execute(_CREATE_ITERATION_RESULTS) 90 91 92class TkoTestMixin(object): 93 def _patch_sqlite_stuff(self): 94 self.god.stub_with(models.TempManager, '_get_column_names', 95 self._get_column_names_for_sqlite3) 96 self.god.stub_with(models.TempManager, '_cursor_rowcount', 97 self._cursor_rowcount_for_sqlite3) 98 99 # add some functions to SQLite for MySQL compatibility 100 connection.cursor() # ensure connection is alive 101 connection.connection.create_function('if', 3, self._sqlite_if) 102 connection.connection.create_function('find_in_set', 2, 103 self._sqlite_find_in_set) 104 105 fix_iteration_tables() 106 107 108 def _cursor_rowcount_for_sqlite3(self, cursor): 109 return len(cursor.fetchall()) 110 111 112 def _sqlite_find_in_set(self, needle, haystack): 113 return needle in haystack.split(',') 114 115 116 def _sqlite_if(self, condition, true_result, false_result): 117 if condition: 118 return true_result 119 return false_result 120 121 122 # sqlite takes any columns that don't have aliases and names them 123 # "table_name"."column_name". we map these to just column_name. 124 _SQLITE_AUTO_COLUMN_ALIAS_RE = re.compile(r'".+"\."(.+)"') 125 126 127 def _get_column_names_for_sqlite3(self, cursor): 128 names = [column_info[0] for column_info in cursor.description] 129 130 # replace all "table_name"."column_name" constructs with just 131 # column_name 132 for i, name in enumerate(names): 133 match = self._SQLITE_AUTO_COLUMN_ALIAS_RE.match(name) 134 if match: 135 names[i] = match.group(1) 136 137 return names 138 139 140 def _create_initial_data(self): 141 machine = models.Machine.objects.create(hostname='myhost') 142 143 # create basic objects 144 kernel_name = 'mykernel1' 145 kernel1 = models.Kernel.objects.create(kernel_hash=kernel_name, 146 base=kernel_name, 147 printable=kernel_name) 148 149 kernel_name = 'mykernel2' 150 kernel2 = models.Kernel.objects.create(kernel_hash=kernel_name, 151 base=kernel_name, 152 printable=kernel_name) 153 154 good_status = models.Status.objects.create(word='GOOD') 155 failed_status = models.Status.objects.create(word='FAILED') 156 157 job1 = models.Job.objects.create(tag='1-myjobtag1', label='myjob1', 158 username='myuser', machine=machine, 159 afe_job_id=1) 160 job2 = models.Job.objects.create(tag='2-myjobtag2', label='myjob2', 161 username='myuser', machine=machine, 162 afe_job_id=2) 163 164 job1_test1 = models.Test.objects.create(job=job1, test='mytest1', 165 kernel=kernel1, 166 status=good_status, 167 machine=machine) 168 self.first_test = job1_test1 169 job1_test2 = models.Test.objects.create(job=job1, test='mytest2', 170 kernel=kernel1, 171 status=failed_status, 172 machine=machine) 173 job2_test1 = models.Test.objects.create(job=job2, test='kernbench', 174 kernel=kernel2, 175 status=good_status, 176 machine=machine) 177 178 job1.jobkeyval_set.create(key='keyval_key', value='keyval_value') 179 180 # create test attributes, test labels, and iterations 181 # like Noah's Ark, include two of each...just in case there's a bug with 182 # multiple related items 183 models.TestAttribute.objects.create(test=job1_test1, attribute='myattr', 184 value='myval') 185 models.TestAttribute.objects.create(test=job1_test1, 186 attribute='myattr2', value='myval2') 187 188 self._add_iteration_keyval('tko_iteration_attributes', test=job1_test1, 189 iteration=1, attribute='iattr', 190 value='ival') 191 self._add_iteration_keyval('tko_iteration_attributes', test=job1_test1, 192 iteration=1, attribute='iattr2', 193 value='ival2') 194 self._add_iteration_keyval('tko_iteration_result', test=job1_test1, 195 iteration=1, attribute='iresult', value=1) 196 self._add_iteration_keyval('tko_iteration_result', test=job1_test1, 197 iteration=1, attribute='iresult2', value=2) 198 self._add_iteration_keyval('tko_iteration_result', test=job1_test1, 199 iteration=2, attribute='iresult', value=3) 200 self._add_iteration_keyval('tko_iteration_result', test=job1_test1, 201 iteration=2, attribute='iresult2', value=4) 202 203 label1 = models.TestLabel.objects.create(name='testlabel1') 204 label2 = models.TestLabel.objects.create(name='testlabel2') 205 206 label1.tests.add(job1_test1) 207 label2.tests.add(job1_test1) 208 209 210 def _add_iteration_keyval(self, table, test, iteration, attribute, value): 211 cursor = connection.cursor() 212 cursor.execute('INSERT INTO %s ' 'VALUES (%%s, %%s, %%s, %%s)' % table, 213 (test.test_idx, iteration, attribute, value)) 214 215 216class RpcInterfaceTest(unittest.TestCase, TkoTestMixin): 217 def setUp(self): 218 self.god = mock.mock_god() 219 220 setup_test_environment.set_up() 221 self._patch_sqlite_stuff() 222 setup_test_view() 223 self._create_initial_data() 224 225 226 def tearDown(self): 227 setup_test_environment.tear_down() 228 self.god.unstub_all() 229 230 231 def _check_for_get_test_views(self, test): 232 self.assertEquals(test['test_name'], 'mytest1') 233 self.assertEquals(test['job_tag'], '1-myjobtag1') 234 self.assertEquals(test['job_name'], 'myjob1') 235 self.assertEquals(test['job_owner'], 'myuser') 236 self.assertEquals(test['status'], 'GOOD') 237 self.assertEquals(test['hostname'], 'myhost') 238 self.assertEquals(test['kernel'], 'mykernel1') 239 240 241 def test_get_detailed_test_views(self): 242 test = rpc_interface.get_detailed_test_views()[0] 243 244 self._check_for_get_test_views(test) 245 246 self.assertEquals(test['attributes'], {'myattr': 'myval', 247 'myattr2': 'myval2'}) 248 self.assertEquals(test['iterations'], [{'attr': {'iattr': 'ival', 249 'iattr2': 'ival2'}, 250 'perf': {'iresult': 1, 251 'iresult2': 2}}, 252 {'attr': {}, 253 'perf': {'iresult': 3, 254 'iresult2': 4}}]) 255 self.assertEquals(test['labels'], ['testlabel1', 'testlabel2']) 256 self.assertEquals(test['job_keyvals'], {'keyval_key': 'keyval_value'}) 257 258 259 def test_test_attributes(self): 260 rpc_interface.set_test_attribute('foo', 'bar', test_name='mytest1') 261 test = rpc_interface.get_detailed_test_views()[0] 262 self.assertEquals(test['attributes'], {'foo': 'bar', 263 'myattr': 'myval', 264 'myattr2': 'myval2'}) 265 266 rpc_interface.set_test_attribute('foo', 'goo', test_name='mytest1') 267 test = rpc_interface.get_detailed_test_views()[0] 268 self.assertEquals(test['attributes'], {'foo': 'goo', 269 'myattr': 'myval', 270 'myattr2': 'myval2'}) 271 272 rpc_interface.set_test_attribute('foo', None, test_name='mytest1') 273 test = rpc_interface.get_detailed_test_views()[0] 274 self.assertEquals(test['attributes'], {'myattr': 'myval', 275 'myattr2': 'myval2'}) 276 277 278 def test_immutable_attributes(self): 279 self.assertRaises(ValueError, rpc_interface.set_test_attribute, 280 'myattr', 'foo', test_name='mytest1') 281 282 283 def test_get_test_views(self): 284 tests = rpc_interface.get_test_views() 285 286 self.assertEquals(len(tests), 3) 287 test = rpc_interface.get_test_views( 288 job_name='myjob1', test_name='mytest1')[0] 289 self.assertEquals(tests[0], test) 290 291 self._check_for_get_test_views(test) 292 293 self.assertEquals( 294 [], rpc_interface.get_test_views(hostname='fakehost')) 295 296 297 def _check_test_names(self, tests, expected_names): 298 self.assertEquals(set(test['test_name'] for test in tests), 299 set(expected_names)) 300 301 302 def test_get_test_views_filter_on_labels(self): 303 tests = rpc_interface.get_test_views(include_labels=['testlabel1']) 304 self._check_test_names(tests, ['mytest1']) 305 306 tests = rpc_interface.get_test_views(exclude_labels=['testlabel1']) 307 self._check_test_names(tests, ['mytest2', 'kernbench']) 308 309 310 def test_get_test_views_filter_on_attributes(self): 311 tests = rpc_interface.get_test_views( 312 include_attributes_where='attribute = "myattr" ' 313 'and value = "myval"') 314 self._check_test_names(tests, ['mytest1']) 315 316 tests = rpc_interface.get_test_views( 317 exclude_attributes_where='attribute="myattr2"') 318 self._check_test_names(tests, ['mytest2', 'kernbench']) 319 320 321 def test_get_num_test_views(self): 322 self.assertEquals(rpc_interface.get_num_test_views(), 3) 323 self.assertEquals(rpc_interface.get_num_test_views( 324 job_name='myjob1', test_name='mytest1'), 1) 325 326 327 def test_get_group_counts(self): 328 self.assertEquals(rpc_interface.get_num_groups(['job_name']), 2) 329 330 counts = rpc_interface.get_group_counts(['job_name']) 331 groups = counts['groups'] 332 self.assertEquals(len(groups), 2) 333 group1, group2 = groups 334 335 self.assertEquals(group1['group_count'], 2) 336 self.assertEquals(group1['job_name'], 'myjob1') 337 self.assertEquals(group2['group_count'], 1) 338 self.assertEquals(group2['job_name'], 'myjob2') 339 340 extra = {'extra' : 'kernel_hash'} 341 counts = rpc_interface.get_group_counts(['job_name'], 342 header_groups=[('job_name',)], 343 extra_select_fields=extra) 344 groups = counts['groups'] 345 self.assertEquals(len(groups), 2) 346 group1, group2 = groups 347 348 self.assertEquals(group1['group_count'], 2) 349 self.assertEquals(group1['header_indices'], [0]) 350 self.assertEquals(group1['extra'], 'mykernel1') 351 self.assertEquals(group2['group_count'], 1) 352 self.assertEquals(group2['header_indices'], [1]) 353 self.assertEquals(group2['extra'], 'mykernel2') 354 355 356 def test_get_status_counts(self): 357 counts = rpc_interface.get_status_counts(group_by=['job_name']) 358 group1, group2 = counts['groups'] 359 self.assertEquals(group1['pass_count'], 1) 360 self.assertEquals(group1['complete_count'], 2) 361 self.assertEquals(group1['incomplete_count'], 0) 362 self.assertEquals(group2['pass_count'], 1) 363 self.assertEquals(group2['complete_count'], 1) 364 self.assertEquals(group2['incomplete_count'], 0) 365 366 367 def test_get_latest_tests(self): 368 counts = rpc_interface.get_latest_tests(group_by=['job_name']) 369 group1, group2 = counts['groups'] 370 self.assertEquals(group1['pass_count'], 0) 371 self.assertEquals(group1['complete_count'], 1) 372 self.assertEquals(group1['test_idx'], 2) 373 self.assertEquals(group2['test_idx'], 3) 374 375 376 def test_get_latest_tests_extra_info(self): 377 counts = rpc_interface.get_latest_tests(group_by=['job_name'], 378 extra_info=['job_tag']) 379 group1, group2 = counts['groups'] 380 self.assertEquals(group1['extra_info'], ['1-myjobtag1']) 381 self.assertEquals(group2['extra_info'], ['2-myjobtag2']) 382 383 384 def test_get_job_ids(self): 385 self.assertEquals([1,2], rpc_interface.get_job_ids()) 386 self.assertEquals([1], rpc_interface.get_job_ids(test_name='mytest2')) 387 388 389 def test_get_hosts_and_tests(self): 390 host_info = rpc_interface.get_hosts_and_tests() 391 self.assertEquals(len(host_info), 1) 392 info = host_info['myhost'] 393 394 self.assertEquals(info['tests'], ['kernbench']) 395 self.assertEquals(info['id'], 1) 396 397 398 def _check_for_get_test_labels(self, label, label_num): 399 self.assertEquals(label['id'], label_num) 400 self.assertEquals(label['description'], '') 401 self.assertEquals(label['name'], 'testlabel%d' % label_num) 402 403 404 def test_test_labels(self): 405 labels = rpc_interface.get_test_labels_for_tests(test_name='mytest1') 406 self.assertEquals(len(labels), 2) 407 label1 = labels[0] 408 label2 = labels[1] 409 410 self._check_for_get_test_labels(label1, 1) 411 self._check_for_get_test_labels(label2, 2) 412 413 rpc_interface.test_label_remove_tests(label1['id'], test_name='mytest1') 414 415 labels = rpc_interface.get_test_labels_for_tests(test_name='mytest1') 416 self.assertEquals(len(labels), 1) 417 label = labels[0] 418 419 self._check_for_get_test_labels(label, 2) 420 421 rpc_interface.test_label_add_tests(label1['id'], test_name='mytest1') 422 423 labels = rpc_interface.get_test_labels_for_tests(test_name='mytest1') 424 self.assertEquals(len(labels), 2) 425 label1 = labels[0] 426 label2 = labels[1] 427 428 self._check_for_get_test_labels(label1, 1) 429 self._check_for_get_test_labels(label2, 2) 430 431 432 def test_get_test_attribute_fields(self): 433 tests = rpc_interface.get_test_views( 434 test_attribute_fields=['myattr', 'myattr2']) 435 self.assertEquals(len(tests), 3) 436 437 self.assertEquals(tests[0]['test_attribute_myattr'], 'myval') 438 self.assertEquals(tests[0]['test_attribute_myattr2'], 'myval2') 439 440 for index in (1, 2): 441 self.assertEquals(tests[index]['test_attribute_myattr'], None) 442 self.assertEquals(tests[index]['test_attribute_myattr2'], None) 443 444 445 def test_filtering_on_test_attribute_fields(self): 446 tests = rpc_interface.get_test_views( 447 extra_where='test_attribute_myattr.value = "myval"', 448 test_attribute_fields=['myattr']) 449 self.assertEquals(len(tests), 1) 450 451 452 def test_grouping_with_test_attribute_fields(self): 453 num_groups = rpc_interface.get_num_groups( 454 ['test_attribute_myattr'], test_attribute_fields=['myattr']) 455 self.assertEquals(num_groups, 2) 456 457 counts = rpc_interface.get_group_counts( 458 ['test_attribute_myattr'], test_attribute_fields=['myattr']) 459 groups = counts['groups'] 460 self.assertEquals(len(groups), num_groups) 461 self.assertEquals(groups[0]['test_attribute_myattr'], None) 462 self.assertEquals(groups[0]['group_count'], 2) 463 self.assertEquals(groups[1]['test_attribute_myattr'], 'myval') 464 self.assertEquals(groups[1]['group_count'], 1) 465 466 467 def test_extra_info_test_attributes(self): 468 counts = rpc_interface.get_latest_tests( 469 group_by=['test_idx'], extra_info=['test_attribute_myattr'], 470 test_attribute_fields=['myattr']) 471 group1 = counts['groups'][0] 472 self.assertEquals(group1['extra_info'], ['myval']) 473 474 475 def test_get_test_label_fields(self): 476 tests = rpc_interface.get_test_views( 477 test_label_fields=['testlabel1', 'testlabel2']) 478 self.assertEquals(len(tests), 3) 479 480 self.assertEquals(tests[0]['test_label_testlabel1'], 'testlabel1') 481 self.assertEquals(tests[0]['test_label_testlabel2'], 'testlabel2') 482 483 for index in (1, 2): 484 self.assertEquals(tests[index]['test_label_testlabel1'], None) 485 self.assertEquals(tests[index]['test_label_testlabel2'], None) 486 487 488 def test_filtering_on_test_label_fields(self): 489 tests = rpc_interface.get_test_views( 490 extra_where='test_label_testlabel1 = "testlabel1"', 491 test_label_fields=['testlabel1']) 492 self.assertEquals(len(tests), 1) 493 494 495 def test_grouping_on_test_label_fields(self): 496 num_groups = rpc_interface.get_num_groups( 497 ['test_label_testlabel1'], test_label_fields=['testlabel1']) 498 self.assertEquals(num_groups, 2) 499 500 counts = rpc_interface.get_group_counts( 501 ['test_label_testlabel1'], test_label_fields=['testlabel1']) 502 groups = counts['groups'] 503 self.assertEquals(len(groups), 2) 504 self.assertEquals(groups[0]['test_label_testlabel1'], None) 505 self.assertEquals(groups[0]['group_count'], 2) 506 self.assertEquals(groups[1]['test_label_testlabel1'], 'testlabel1') 507 self.assertEquals(groups[1]['group_count'], 1) 508 509 510 def test_get_iteration_result_fields(self): 511 num_iterations = rpc_interface.get_num_test_views( 512 iteration_result_fields=['iresult', 'iresult2']) 513 self.assertEquals(num_iterations, 2) 514 515 iterations = rpc_interface.get_test_views( 516 iteration_result_fields=['iresult', 'iresult2']) 517 self.assertEquals(len(iterations), 2) 518 519 for index in (0, 1): 520 self.assertEquals(iterations[index]['test_idx'], 1) 521 522 self.assertEquals(iterations[0]['iteration_index'], 1) 523 self.assertEquals(iterations[0]['iteration_result_iresult'], 1) 524 self.assertEquals(iterations[0]['iteration_result_iresult2'], 2) 525 526 self.assertEquals(iterations[1]['iteration_index'], 2) 527 self.assertEquals(iterations[1]['iteration_result_iresult'], 3) 528 self.assertEquals(iterations[1]['iteration_result_iresult2'], 4) 529 530 531 def test_filtering_on_iteration_result_fields(self): 532 iterations = rpc_interface.get_test_views( 533 extra_where='iteration_result_iresult.value = 1', 534 iteration_result_fields=['iresult']) 535 self.assertEquals(len(iterations), 1) 536 537 538 def test_grouping_with_iteration_result_fields(self): 539 num_groups = rpc_interface.get_num_groups( 540 ['iteration_result_iresult'], 541 iteration_result_fields=['iresult']) 542 self.assertEquals(num_groups, 2) 543 544 counts = rpc_interface.get_group_counts( 545 ['iteration_result_iresult'], 546 iteration_result_fields=['iresult']) 547 groups = counts['groups'] 548 self.assertEquals(len(groups), 2) 549 self.assertEquals(groups[0]['iteration_result_iresult'], 1) 550 self.assertEquals(groups[0]['group_count'], 1) 551 self.assertEquals(groups[1]['iteration_result_iresult'], 3) 552 self.assertEquals(groups[1]['group_count'], 1) 553 554 555 def _setup_machine_labels(self): 556 models.TestAttribute.objects.create(test=self.first_test, 557 attribute='host-labels', 558 value='label1,label2') 559 560 561 def test_get_machine_label_fields(self): 562 self._setup_machine_labels() 563 564 tests = rpc_interface.get_test_views( 565 machine_label_fields=['label1', 'otherlabel']) 566 self.assertEquals(len(tests), 3) 567 568 self.assertEquals(tests[0]['machine_label_label1'], 'label1') 569 self.assertEquals(tests[0]['machine_label_otherlabel'], None) 570 571 for index in (1, 2): 572 self.assertEquals(tests[index]['machine_label_label1'], None) 573 self.assertEquals(tests[index]['machine_label_otherlabel'], None) 574 575 576 def test_grouping_with_machine_label_fields(self): 577 self._setup_machine_labels() 578 579 counts = rpc_interface.get_group_counts(['machine_label_label1'], 580 machine_label_fields=['label1']) 581 groups = counts['groups'] 582 self.assertEquals(len(groups), 2) 583 self.assertEquals(groups[0]['machine_label_label1'], None) 584 self.assertEquals(groups[0]['group_count'], 2) 585 self.assertEquals(groups[1]['machine_label_label1'], 'label1') 586 self.assertEquals(groups[1]['group_count'], 1) 587 588 589 def test_filtering_on_machine_label_fields(self): 590 self._setup_machine_labels() 591 592 tests = rpc_interface.get_test_views( 593 extra_where='machine_label_label1 = "label1"', 594 machine_label_fields=['label1']) 595 self.assertEquals(len(tests), 1) 596 597 598 def test_quoting_fields(self): 599 # ensure fields with special characters are properly quoted throughout 600 rpc_interface.add_test_label('hyphen-label') 601 rpc_interface.get_group_counts( 602 ['test_attribute_hyphen-attr', 'test_label_hyphen-label', 603 'machine_label_hyphen-label', 604 'iteration_result_hyphen-result'], 605 test_attribute_fields=['hyphen-attr'], 606 test_label_fields=['hyphen-label'], 607 machine_label_fields=['hyphen-label'], 608 iteration_result_fields=['hyphen-result']) 609 610 611if __name__ == '__main__': 612 unittest.main() 613