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