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