1#!/usr/bin/python 2 3import datetime 4import unittest 5import common 6from autotest_lib.frontend import setup_django_environment 7from autotest_lib.frontend.afe import frontend_test_utils 8from autotest_lib.frontend.afe import models, model_attributes, model_logic 9from autotest_lib.client.common_lib import global_config 10from autotest_lib.client.common_lib import control_data 11 12 13class AclGroupTest(unittest.TestCase, 14 frontend_test_utils.FrontendTestMixin): 15 def setUp(self): 16 self._frontend_common_setup() 17 18 19 def tearDown(self): 20 self._frontend_common_teardown() 21 22 23 def _check_acls(self, host, acl_name_list): 24 actual_acl_names = [acl_group.name for acl_group 25 in host.aclgroup_set.all()] 26 self.assertEquals(set(actual_acl_names), set(acl_name_list)) 27 28 29 def test_on_host_membership_change(self): 30 host1, host2 = self.hosts[1:3] 31 everyone_acl = models.AclGroup.objects.get(name='Everyone') 32 33 host1.aclgroup_set.clear() 34 self._check_acls(host1, []) 35 host2.aclgroup_set.add(everyone_acl) 36 self._check_acls(host2, ['Everyone', 'my_acl']) 37 38 models.AclGroup.on_host_membership_change() 39 40 self._check_acls(host1, ['Everyone']) 41 self._check_acls(host2, ['my_acl']) 42 43 44class HostTest(unittest.TestCase, 45 frontend_test_utils.FrontendTestMixin): 46 def setUp(self): 47 self._frontend_common_setup() 48 49 50 def tearDown(self): 51 self._frontend_common_teardown() 52 53 54 def test_add_host_previous_one_time_host(self): 55 # ensure that when adding a host which was previously used as a one-time 56 # host, the status isn't reset, since this can interfere with the 57 # scheduler. 58 host = models.Host.create_one_time_host('othost') 59 self.assertEquals(host.invalid, True) 60 self.assertEquals(host.status, models.Host.Status.READY) 61 62 host.status = models.Host.Status.RUNNING 63 host.save() 64 65 host2 = models.Host.add_object(hostname='othost') 66 self.assertEquals(host2.id, host.id) 67 self.assertEquals(host2.status, models.Host.Status.RUNNING) 68 69 70class SpecialTaskUnittest(unittest.TestCase, 71 frontend_test_utils.FrontendTestMixin): 72 def setUp(self): 73 self._frontend_common_setup() 74 75 76 def tearDown(self): 77 self._frontend_common_teardown() 78 79 80 def _create_task(self): 81 return models.SpecialTask.objects.create( 82 host=self.hosts[0], task=models.SpecialTask.Task.VERIFY, 83 requested_by=models.User.current_user()) 84 85 86 def test_execution_path(self): 87 task = self._create_task() 88 self.assertEquals(task.execution_path(), 'hosts/host1/1-verify') 89 90 91 def test_status(self): 92 task = self._create_task() 93 self.assertEquals(task.status, 'Queued') 94 95 task.update_object(is_active=True) 96 self.assertEquals(task.status, 'Running') 97 98 task.update_object(is_active=False, is_complete=True, success=True) 99 self.assertEquals(task.status, 'Completed') 100 101 task.update_object(success=False) 102 self.assertEquals(task.status, 'Failed') 103 104 105 def test_activate(self): 106 task = self._create_task() 107 task.activate() 108 self.assertTrue(task.is_active) 109 self.assertFalse(task.is_complete) 110 111 112 def test_finish(self): 113 task = self._create_task() 114 task.activate() 115 task.finish(True) 116 self.assertFalse(task.is_active) 117 self.assertTrue(task.is_complete) 118 self.assertTrue(task.success) 119 120 121 def test_requested_by_from_queue_entry(self): 122 job = self._create_job(hosts=[0]) 123 task = models.SpecialTask.objects.create( 124 host=self.hosts[0], task=models.SpecialTask.Task.VERIFY, 125 queue_entry=job.hostqueueentry_set.all()[0]) 126 self.assertEquals(task.requested_by.login, 'autotest_system') 127 128 129class HostQueueEntryUnittest(unittest.TestCase, 130 frontend_test_utils.FrontendTestMixin): 131 def setUp(self): 132 self._frontend_common_setup() 133 134 135 def tearDown(self): 136 self._frontend_common_teardown() 137 138 139 def test_execution_path(self): 140 entry = self._create_job(hosts=[1]).hostqueueentry_set.all()[0] 141 entry.execution_subdir = 'subdir' 142 entry.save() 143 144 self.assertEquals(entry.execution_path(), '1-autotest_system/subdir') 145 146 147class ModelWithInvalidTest(unittest.TestCase, 148 frontend_test_utils.FrontendTestMixin): 149 def setUp(self): 150 self._frontend_common_setup() 151 152 153 def tearDown(self): 154 self._frontend_common_teardown() 155 156 157 def test_model_with_invalid_delete(self): 158 self.assertFalse(self.hosts[0].invalid) 159 self.hosts[0].delete() 160 self.assertTrue(self.hosts[0].invalid) 161 self.assertTrue(models.Host.objects.get(id=self.hosts[0].id)) 162 163 164 def test_model_with_invalid_delete_queryset(self): 165 for host in self.hosts: 166 self.assertFalse(host.invalid) 167 168 hosts = models.Host.objects.all() 169 hosts.delete() 170 self.assertEqual(hosts.count(), len(self.hosts)) 171 172 for host in hosts: 173 self.assertTrue(host.invalid) 174 175 176 def test_cloned_queryset_delete(self): 177 """ 178 Make sure that a cloned queryset maintains the custom delete() 179 """ 180 to_delete = ('host1', 'host2') 181 182 for host in self.hosts: 183 self.assertFalse(host.invalid) 184 185 hosts = models.Host.objects.all().filter(hostname__in=to_delete) 186 hosts.delete() 187 all_hosts = models.Host.objects.all() 188 self.assertEqual(all_hosts.count(), len(self.hosts)) 189 190 for host in all_hosts: 191 if host.hostname in to_delete: 192 self.assertTrue( 193 host.invalid, 194 '%s.invalid expected to be True' % host.hostname) 195 else: 196 self.assertFalse( 197 host.invalid, 198 '%s.invalid expected to be False' % host.hostname) 199 200 201 def test_normal_delete(self): 202 job = self._create_job(hosts=[1]) 203 self.assertEqual(1, models.Job.objects.all().count()) 204 205 job.delete() 206 self.assertEqual(0, models.Job.objects.all().count()) 207 208 209 def test_normal_delete_queryset(self): 210 self._create_job(hosts=[1]) 211 self._create_job(hosts=[2]) 212 213 self.assertEqual(2, models.Job.objects.all().count()) 214 215 models.Job.objects.all().delete() 216 self.assertEqual(0, models.Job.objects.all().count()) 217 218 219class KernelTest(unittest.TestCase, frontend_test_utils.FrontendTestMixin): 220 def setUp(self): 221 self._frontend_common_setup() 222 223 224 def tearDown(self): 225 self._frontend_common_teardown() 226 227 228 def test_create_kernels_none(self): 229 self.assertEqual(None, models.Kernel.create_kernels(None)) 230 231 232 def test_create_kernels(self): 233 self.god.stub_function(models.Kernel, '_create') 234 235 num_kernels = 3 236 kernel_list = [object() for _ in range(num_kernels)] 237 result = [object() for _ in range(num_kernels)] 238 239 for kernel, response in zip(kernel_list, result): 240 models.Kernel._create.expect_call(kernel).and_return(response) 241 self.assertEqual(result, models.Kernel.create_kernels(kernel_list)) 242 self.god.check_playback() 243 244 245 def test_create(self): 246 kernel = models.Kernel._create({'version': 'version'}) 247 self.assertEqual(kernel.version, 'version') 248 self.assertEqual(kernel.cmdline, '') 249 self.assertEqual(kernel, models.Kernel._create({'version': 'version'})) 250 251 252class ParameterizedJobTest(unittest.TestCase, 253 frontend_test_utils.FrontendTestMixin): 254 def setUp(self): 255 self._frontend_common_setup() 256 257 258 def tearDown(self): 259 self._frontend_common_teardown() 260 261 262 def test_job(self): 263 global_config.global_config.override_config_value( 264 'AUTOTEST_WEB', 'parameterized_jobs', 'True') 265 266 test = models.Test.objects.create( 267 name='name', author='author', test_class='class', 268 test_category='category', 269 test_type=control_data.CONTROL_TYPE.SERVER, path='path') 270 parameterized_job = models.ParameterizedJob.objects.create(test=test) 271 job = self._create_job(hosts=[1], control_file=None, 272 parameterized_job=parameterized_job) 273 274 self.assertEqual(job, parameterized_job.job()) 275 276 277class JobTest(unittest.TestCase, frontend_test_utils.FrontendTestMixin): 278 def setUp(self): 279 self._frontend_common_setup() 280 281 282 def tearDown(self): 283 self._frontend_common_teardown() 284 285 286 def test_check_parameterized_jobs_no_args(self): 287 self.assertRaises(Exception, models.Job.check_parameterized_job, 288 control_file=None, parameterized_job=None) 289 290 291 def test_check_parameterized_jobs_both_args(self): 292 self.assertRaises(Exception, models.Job.check_parameterized_job, 293 control_file=object(), parameterized_job=object()) 294 295 296 def test_check_parameterized_jobs_disabled(self): 297 self.assertRaises(Exception, models.Job.check_parameterized_job, 298 control_file=None, parameterized_job=object()) 299 300 301 def test_check_parameterized_jobs_enabled(self): 302 global_config.global_config.override_config_value( 303 'AUTOTEST_WEB', 'parameterized_jobs', 'True') 304 self.assertRaises(Exception, models.Job.check_parameterized_job, 305 control_file=object(), parameterized_job=None) 306 307 308class SerializationTest(unittest.TestCase, 309 frontend_test_utils.FrontendTestMixin): 310 def setUp(self): 311 self._frontend_common_setup(fill_data=False) 312 313 314 def tearDown(self): 315 self._frontend_common_teardown() 316 317 318 def _get_example_response(self): 319 return {'hosts': [{'aclgroup_set': [{'description': '', 320 'id': 1, 321 'name': 'Everyone', 322 'users': [{ 323 'access_level': 100, 324 'id': 1, 325 'login': 'autotest_system', 326 'reboot_after': 0, 327 'reboot_before': 1, 328 'show_experimental': False}]}], 329 'dirty': True, 330 'hostattribute_set': [], 331 'hostname': '100.107.2.163', 332 'id': 2, 333 'invalid': False, 334 'labels': [{'id': 7, 335 'invalid': False, 336 'kernel_config': '', 337 'name': 'power:battery', 338 'only_if_needed': False, 339 'platform': False}, 340 {'id': 9, 341 'invalid': False, 342 'kernel_config': '', 343 'name': 'hw_video_acc_h264', 344 'only_if_needed': False, 345 'platform': False}, 346 {'id': 10, 347 'invalid': False, 348 'kernel_config': '', 349 'name': 'hw_video_acc_enc_h264', 350 'only_if_needed': False, 351 'platform': False}, 352 {'id': 11, 353 'invalid': False, 354 'kernel_config': '', 355 'name': 'webcam', 356 'only_if_needed': False, 357 'platform': False}, 358 {'id': 12, 359 'invalid': False, 360 'kernel_config': '', 361 'name': 'touchpad', 362 'only_if_needed': False, 363 'platform': False}, 364 {'id': 13, 365 'invalid': False, 366 'kernel_config': '', 367 'name': 'spring', 368 'only_if_needed': False, 369 'platform': False}, 370 {'id': 14, 371 'invalid': False, 372 'kernel_config': '', 373 'name': 'board:daisy', 374 'only_if_needed': False, 375 'platform': True}, 376 {'id': 15, 377 'invalid': False, 378 'kernel_config': '', 379 'name': 'board_freq_mem:daisy_1.7GHz', 380 'only_if_needed': False, 381 'platform': False}, 382 {'id': 16, 383 'invalid': False, 384 'kernel_config': '', 385 'name': 'bluetooth', 386 'only_if_needed': False, 387 'platform': False}, 388 {'id': 17, 389 'invalid': False, 390 'kernel_config': '', 391 'name': 'gpu_family:mali', 392 'only_if_needed': False, 393 'platform': False}, 394 {'id': 19, 395 'invalid': False, 396 'kernel_config': '', 397 'name': 'ec:cros', 398 'only_if_needed': False, 399 'platform': False}, 400 {'id': 20, 401 'invalid': False, 402 'kernel_config': '', 403 'name': 'storage:mmc', 404 'only_if_needed': False, 405 'platform': False}, 406 {'id': 21, 407 'invalid': False, 408 'kernel_config': '', 409 'name': 'hw_video_acc_vp8', 410 'only_if_needed': False, 411 'platform': False}, 412 {'id': 22, 413 'invalid': False, 414 'kernel_config': '', 415 'name': 'video_glitch_detection', 416 'only_if_needed': False, 417 'platform': False}, 418 {'id': 23, 419 'invalid': False, 420 'kernel_config': '', 421 'name': 'pool:suites', 422 'only_if_needed': False, 423 'platform': False}, 424 {'id': 25, 425 'invalid': False, 426 'kernel_config': '', 427 'name': 'daisy-board-name', 428 'only_if_needed': False, 429 'platform': False}], 430 'leased': False, 431 'lock_reason': '', 432 'lock_time': None, 433 'locked': False, 434 'protection': 0, 435 'shard': {'hostname': '1', 'id': 1}, 436 'status': 'Ready', 437 'synch_id': None}], 438 'jobs': [{'control_file': 'some control file\n\n\n', 439 'control_type': 2, 440 'created_on': '2014-09-04T13:09:35', 441 'dependency_labels': [{'id': 14, 442 'invalid': False, 443 'kernel_config': '', 444 'name': 'board:daisy', 445 'only_if_needed': False, 446 'platform': True}, 447 {'id': 23, 448 'invalid': False, 449 'kernel_config': '', 450 'name': 'pool:suites', 451 'only_if_needed': False, 452 'platform': False}, 453 {'id': 25, 454 'invalid': False, 455 'kernel_config': '', 456 'name': 'daisy-board-name', 457 'only_if_needed': False, 458 'platform': False}], 459 'email_list': '', 460 'hostqueueentry_set': [{'aborted': False, 461 'active': False, 462 'complete': False, 463 'deleted': False, 464 'execution_subdir': '', 465 'finished_on': None, 466 'id': 5, 467 'meta_host': { 468 'id': 14, 469 'invalid': False, 470 'kernel_config': '', 471 'name': 'board:daisy', 472 'only_if_needed': False, 473 'platform': True}, 474 'host_id': None, 475 'started_on': None, 476 'status': 'Queued'}], 477 'id': 5, 478 'jobkeyval_set': [{'id': 10, 479 'job_id': 5, 480 'key': 'suite', 481 'value': 'dummy'}, 482 {'id': 11, 483 'job_id': 5, 484 'key': 'build', 485 'value': 'daisy-release'}, 486 {'id': 12, 487 'job_id': 5, 488 'key': 'experimental', 489 'value': 'False'}], 490 'max_runtime_hrs': 72, 491 'max_runtime_mins': 1440, 492 'name': 'daisy-experimental', 493 'owner': 'autotest', 494 'parse_failed_repair': True, 495 'priority': 40, 496 'reboot_after': 0, 497 'reboot_before': 1, 498 'run_reset': True, 499 'run_verify': False, 500 'shard': {'hostname': '1', 'id': 1}, 501 'synch_count': 1, 502 'test_retry': 0, 503 'timeout': 24, 504 'timeout_mins': 1440, 505 'require_ssp': None}, 506 {'control_file': 'some control file\n\n\n', 507 'control_type': 2, 508 'created_on': '2014-09-04T13:09:35', 509 'dependency_labels': [{'id': 14, 510 'invalid': False, 511 'kernel_config': '', 512 'name': 'board:daisy', 513 'only_if_needed': False, 514 'platform': True}, 515 {'id': 23, 516 'invalid': False, 517 'kernel_config': '', 518 'name': 'pool:suites', 519 'only_if_needed': False, 520 'platform': False}, 521 {'id': 25, 522 'invalid': False, 523 'kernel_config': '', 524 'name': 'daisy-board-name', 525 'only_if_needed': False, 526 'platform': False}], 527 'email_list': '', 528 'hostqueueentry_set': [{'aborted': False, 529 'active': False, 530 'complete': False, 531 'deleted': False, 532 'execution_subdir': '', 533 'finished_on': None, 534 'id': 7, 535 'meta_host': { 536 'id': 14, 537 'invalid': False, 538 'kernel_config': '', 539 'name': 'board:daisy', 540 'only_if_needed': False, 541 'platform': True}, 542 'host_id': None, 543 'started_on': None, 544 'status': 'Queued'}], 545 'id': 7, 546 'jobkeyval_set': [{'id': 16, 547 'job_id': 7, 548 'key': 'suite', 549 'value': 'dummy'}, 550 {'id': 17, 551 'job_id': 7, 552 'key': 'build', 553 'value': 'daisy-release'}, 554 {'id': 18, 555 'job_id': 7, 556 'key': 'experimental', 557 'value': 'False'}], 558 'max_runtime_hrs': 72, 559 'max_runtime_mins': 1440, 560 'name': 'daisy-experimental', 561 'owner': 'autotest', 562 'parse_failed_repair': True, 563 'priority': 40, 564 'reboot_after': 0, 565 'reboot_before': 1, 566 'run_reset': True, 567 'run_verify': False, 568 'shard': {'hostname': '1', 'id': 1}, 569 'synch_count': 1, 570 'test_retry': 0, 571 'timeout': 24, 572 'timeout_mins': 1440, 573 'require_ssp': None}]} 574 575 576 def test_response(self): 577 heartbeat_response = self._get_example_response() 578 hosts_serialized = heartbeat_response['hosts'] 579 jobs_serialized = heartbeat_response['jobs'] 580 581 # Persisting is automatically done inside deserialize 582 hosts = [models.Host.deserialize(host) for host in hosts_serialized] 583 jobs = [models.Job.deserialize(job) for job in jobs_serialized] 584 585 generated_heartbeat_response = { 586 'hosts': [host.serialize() for host in hosts], 587 'jobs': [job.serialize() for job in jobs] 588 } 589 example_response = self._get_example_response() 590 # For attribute-like objects, we don't care about its id. 591 for r in [generated_heartbeat_response, example_response]: 592 for job in r['jobs']: 593 for keyval in job['jobkeyval_set']: 594 keyval.pop('id') 595 for host in r['hosts']: 596 for attribute in host['hostattribute_set']: 597 keyval.pop('id') 598 self.assertEqual(generated_heartbeat_response, example_response) 599 600 601 def test_update(self): 602 job = self._create_job(hosts=[1]) 603 serialized = job.serialize(include_dependencies=False) 604 serialized['owner'] = 'some_other_owner' 605 606 job.update_from_serialized(serialized) 607 self.assertEqual(job.owner, 'some_other_owner') 608 609 serialized = job.serialize() 610 self.assertRaises( 611 ValueError, 612 job.update_from_serialized, serialized) 613 614 615 def test_sync_aborted(self): 616 job = self._create_job(hosts=[1]) 617 serialized = job.serialize() 618 619 serialized['hostqueueentry_set'][0]['aborted'] = True 620 serialized['hostqueueentry_set'][0]['status'] = 'Running' 621 622 models.Job.deserialize(serialized) 623 624 job = models.Job.objects.get(pk=job.id) 625 self.assertTrue(job.hostqueueentry_set.all()[0].aborted) 626 self.assertEqual(job.hostqueueentry_set.all()[0].status, 'Queued') 627 628 629if __name__ == '__main__': 630 unittest.main() 631