Home | History | Annotate | Download | only in site_utils
      1 #!/usr/bin/python
      2 # Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 import collections
      7 import datetime as datetime_base
      8 from datetime import datetime
      9 import mock
     10 import time
     11 import unittest
     12 
     13 import common
     14 
     15 from autotest_lib.server.cros.dynamic_suite import constants
     16 from autotest_lib.site_utils import run_suite
     17 from autotest_lib.site_utils import run_suite_common
     18 from autotest_lib.site_utils import diagnosis_utils
     19 
     20 
     21 class ReturnResultUnittest(unittest.TestCase):
     22     """_ReturnResult tests."""
     23 
     24     def setUp(self):
     25         super(ReturnResultUnittest, self).setUp()
     26         patcher = mock.patch.object(run_suite, '_RETURN_RESULTS',
     27                                     collections.OrderedDict())
     28         self.results = results = patcher.start()
     29         self.addCleanup(patcher.stop)
     30         results['small'] = run_suite._ReturnResult(0, 'small')
     31         results['big'] = run_suite._ReturnResult(1, 'big')
     32 
     33         patcher = mock.patch.object(run_suite, '_RETURN_RESULTS_LIST',
     34                                     list(results.values()))
     35         patcher.start()
     36         self.addCleanup(patcher.stop)
     37 
     38     def test_equal(self):
     39         """Test _ReturnResult equal."""
     40         self.assertEqual(self.results['small'], self.results['small'])
     41 
     42     def test_unequal(self):
     43         """Test _ReturnResult unequal."""
     44         self.assertNotEqual(self.results['big'], self.results['small'])
     45 
     46     def test_greater_than(self):
     47         """Test _ReturnResult greater than."""
     48         self.assertGreater(self.results['big'], self.results['small'])
     49 
     50     def test_bitwise_or(self):
     51         """Test _ReturnResult bitwise or."""
     52         self.assertEqual(self.results['big'],
     53                          self.results['big'] | self.results['small'])
     54 
     55 
     56 class ResultCollectorUnittest(unittest.TestCase):
     57     """Runsuite unittest"""
     58 
     59     JOB_MAX_RUNTIME_MINS = 10
     60 
     61     def setUp(self):
     62         """Set up test."""
     63         self.afe = mock.MagicMock()
     64         self.tko = mock.MagicMock()
     65 
     66 
     67     def _build_view(self, test_idx, test_name, subdir, status, afe_job_id,
     68                     job_name='fake_job_name', reason='fake reason',
     69                     job_keyvals=None, test_started_time=None,
     70                     test_finished_time=None, invalidates_test_idx=None,
     71                     job_started_time=None, job_finished_time=None):
     72         """Build a test view using the given fields.
     73 
     74         @param test_idx: An integer representing test_idx.
     75         @param test_name: A string, e.g. 'dummy_Pass'
     76         @param subdir: A string representing the subdir field of the test view.
     77                        e.g. 'dummy_Pass'.
     78         @param status: A string representing the test status.
     79                        e.g. 'FAIL', 'PASS'
     80         @param afe_job_id: An integer representing the afe job id.
     81         @param job_name: A string representing the job name.
     82         @param reason: A string representing the reason field of the test view.
     83         @param job_keyvals: A dictionary stroing the job keyvals.
     84         @param test_started_time: A string, e.g. '2014-04-12 12:35:33'
     85         @param test_finished_time: A string, e.g. '2014-04-12 12:35:33'
     86         @param invalidates_test_idx: An integer, representing the idx of the
     87                                      test that has been retried.
     88         @param job_started_time: A string, e.g. '2014-04-12 12:35:33'
     89         @param job_finished_time: A string, e.g. '2014-04-12 12:35:33'
     90 
     91         @reutrn: A dictionary representing a test view.
     92 
     93         """
     94         if job_keyvals is None:
     95             job_keyvals = {}
     96         return {'test_idx': test_idx, 'test_name': test_name, 'subdir':subdir,
     97                 'status': status, 'afe_job_id': afe_job_id,
     98                 'job_name': job_name, 'reason': reason,
     99                 'job_keyvals': job_keyvals,
    100                 'test_started_time': test_started_time,
    101                 'test_finished_time': test_finished_time,
    102                 'invalidates_test_idx': invalidates_test_idx,
    103                 'job_started_time': job_started_time,
    104                 'job_finished_time': job_finished_time}
    105 
    106 
    107     def _mock_tko_get_detailed_test_views(self, test_views,
    108                                           missing_results=[]):
    109         """Mock tko method get_detailed_test_views call.
    110 
    111         @param test_views: A list of test views that will be returned
    112                            by get_detailed_test_views.
    113         """
    114         return_values = {}
    115         for v in test_views:
    116             views_of_job = return_values.setdefault(
    117                     ('get_detailed_test_views', v['afe_job_id']), [])
    118             views_of_job.append(v)
    119         for job_id in missing_results:
    120             views_of_job = return_values.setdefault(
    121                     ('get_detailed_test_views', job_id), [])
    122 
    123         def side_effect(*args, **kwargs):
    124             """Maps args and kwargs to the mocked return values."""
    125             key = (kwargs['call'], kwargs['afe_job_id'])
    126             return return_values[key]
    127 
    128         self.tko.run = mock.MagicMock(side_effect=side_effect)
    129 
    130 
    131     def _mock_afe_get_jobs(self, suite_job_id, child_job_ids):
    132         """Mock afe get_jobs call.
    133 
    134         @param suite_job_id: The afe job id of the suite job.
    135         @param child_job_ids: A list of job ids of the child jobs.
    136 
    137         """
    138         suite_job = mock.MagicMock()
    139         suite_job.id = suite_job_id
    140         suite_job.max_runtime_mins = 10
    141         suite_job.parent_job = None
    142 
    143         return_values = {suite_job_id: []}
    144         for job_id in child_job_ids:
    145             new_job = mock.MagicMock()
    146             new_job.id = job_id
    147             new_job.name = 'test.%d' % job_id
    148             new_job.max_runtime_mins = self.JOB_MAX_RUNTIME_MINS
    149             new_job.parent_job = suite_job
    150             return_values[suite_job_id].append(new_job)
    151 
    152         def side_effect(*args, **kwargs):
    153             """Maps args and kwargs to the mocked return values."""
    154             if kwargs.get('id') == suite_job_id:
    155                 return [suite_job]
    156             return return_values[kwargs['parent_job_id']]
    157 
    158         self.afe.get_jobs = mock.MagicMock(side_effect=side_effect)
    159 
    160 
    161     def testFetchSuiteTestView(self):
    162         """Test that it fetches the correct suite test views."""
    163         suite_job_id = 100
    164         suite_name = 'dummy'
    165         build = 'R23-1.1.1.1'
    166         server_job_view = self._build_view(
    167                 10, 'SERVER_JOB', '----', 'GOOD', suite_job_id)
    168         test_to_ignore = self._build_view(
    169                 11, 'dummy_Pass', '101-user/host/dummy_Pass',
    170                 'GOOD', suite_job_id)
    171         test_to_include = self._build_view(
    172                 12, 'dummy_Pass.bluetooth', None, 'TEST_NA', suite_job_id)
    173         test_missing = self._build_view(
    174                 13, 'dummy_Missing', None, 'ABORT', suite_job_id)
    175         self._mock_afe_get_jobs(suite_job_id, [])
    176         self._mock_tko_get_detailed_test_views(
    177                 [server_job_view, test_to_ignore, test_to_include,
    178                  test_missing])
    179         collector = run_suite.ResultCollector(
    180                 'fake_server', self.afe, self.tko,
    181                 build=build, suite_name=suite_name,
    182                 suite_job_id=suite_job_id,
    183                 return_code_function=run_suite._ReturnCodeComputer())
    184         collector._missing_results = {
    185                 test_missing['test_name']: [14, 15],
    186         }
    187         suite_views = collector._fetch_relevant_test_views_of_suite()
    188         suite_views = sorted(suite_views, key=lambda view: view['test_idx'])
    189         # Verify that SERVER_JOB is renamed to 'Suite job'
    190         self.assertEqual(suite_views[0].get_testname(),
    191                          run_suite.TestView.SUITE_JOB)
    192         # Verify that the test with a subidr is not included.
    193         self.assertEqual(suite_views[0]['test_idx'], 10)
    194         self.assertEqual(suite_views[1]['test_idx'], 12)
    195         self.assertEqual(suite_views[1]['afe_job_id'], suite_job_id)
    196         # Verify that the test with missing results had it's AFE job id
    197         # replaced.
    198         self.assertEqual(suite_views[2]['test_idx'], 13)
    199         self.assertEqual(suite_views[2]['afe_job_id'], 14)
    200 
    201 
    202     def testFetchTestViewOfChildJobs(self):
    203         """Test that it fetches the correct child test views."""
    204         build = 'lumpy-release/R36-5788.0.0'
    205         board = 'lumpy'
    206         suite_name = 'my_suite'
    207         suite_job_id = 100
    208         invalid_job_id = 101
    209         invalid_job_name = '%s/%s/test_Pass' % (build, suite_name)
    210         good_job_id = 102
    211         good_job_name = '%s/%s/test_Pass' % (build, suite_name)
    212         bad_job_id = 103
    213         bad_job_name = '%s/%s/test_ServerJobFail' % (build, suite_name)
    214         missing_job_id = 104
    215 
    216         invalid_test = self._build_view(
    217                 19, 'test_Pass_Old', 'fake/subdir',
    218                 'FAIL', invalid_job_id, invalid_job_name)
    219         good_job_server_job = self._build_view(
    220                 20, 'SERVER_JOB', '----', 'GOOD', good_job_id, good_job_name)
    221         good_job_test = self._build_view(
    222                 21, 'test_Pass', 'fake/subdir', 'GOOD',
    223                 good_job_id, good_job_name,
    224                 job_keyvals={'retry_original_job_id': invalid_job_id})
    225         bad_job_server_job = self._build_view(
    226                 22, 'SERVER_JOB', '----', 'FAIL', bad_job_id, bad_job_name)
    227         bad_job_test = self._build_view(
    228                 23, 'test_ServerJobFail', 'fake/subdir', 'GOOD',
    229                 bad_job_id, bad_job_name)
    230         self._mock_tko_get_detailed_test_views(
    231                 [good_job_server_job, good_job_test,
    232                  bad_job_server_job, bad_job_test, invalid_test],
    233                 [missing_job_id])
    234         self._mock_afe_get_jobs(suite_job_id,
    235                                 [good_job_id, bad_job_id, missing_job_id])
    236         collector = run_suite.ResultCollector(
    237                 'fake_server', self.afe, self.tko,
    238                 build, suite_name, suite_job_id,
    239                 return_code_function=run_suite._ReturnCodeComputer())
    240         child_views, retry_counts, missing_results = (
    241                 collector._fetch_test_views_of_child_jobs())
    242         # child_views should contain tests 21, 22, 23
    243         child_views = sorted(child_views, key=lambda view: view['test_idx'])
    244         # Verify that the SERVER_JOB has been renamed properly
    245         self.assertEqual(child_views[1].get_testname(),
    246                          'test_ServerJobFail_SERVER_JOB')
    247         self.assertEqual(missing_results, {'test.104': [104]})
    248         # Verify that failed SERVER_JOB and actual invalid tests are included,
    249         expected = [good_job_test['test_idx'], bad_job_server_job['test_idx'],
    250                     bad_job_test['test_idx']]
    251         child_view_ids = [v['test_idx'] for v in child_views]
    252         self.assertEqual(child_view_ids, expected)
    253         self.afe.get_jobs.assert_called_once_with(
    254                 parent_job_id=suite_job_id)
    255         # Verify the retry_counts is calculated correctly
    256         self.assertEqual(len(retry_counts), 1)
    257         self.assertEqual(retry_counts[21], 1)
    258 
    259 
    260     def testGenerateLinks(self):
    261         """Test that it generates correct web and buildbot links."""
    262         suite_job_id = 100
    263         suite_name = 'my_suite'
    264         build = 'lumpy-release/R36-5788.0.0'
    265         board = 'lumpy'
    266         fake_job = mock.MagicMock()
    267         fake_job.parent = suite_job_id
    268         test_sponge_url = 'http://test_url'
    269         job_keyvals = {'sponge_url': test_sponge_url}
    270         suite_job_view = run_suite.TestView(
    271                 self._build_view(
    272                     20, 'Suite job', '----', 'GOOD', suite_job_id,
    273                     job_keyvals=job_keyvals),
    274                 fake_job, suite_name, build, 'chromeos-test')
    275         good_test = run_suite.TestView(
    276                 self._build_view(
    277                     21, 'test_Pass', 'fake/subdir', 'GOOD', 101,
    278                     job_keyvals=job_keyvals),
    279                 fake_job, suite_name, build, 'chromeos-test')
    280         bad_test = run_suite.TestView(
    281                 self._build_view(
    282                     23, 'test_Fail', 'fake/subdir', 'FAIL', 102,
    283                     job_keyvals=job_keyvals),
    284                 fake_job, suite_name, build, 'chromeos-test')
    285 
    286         collector = run_suite.ResultCollector(
    287                 'fake_server', self.afe, self.tko,
    288                 build, suite_name, suite_job_id, user='chromeos-test',
    289                 return_code_function=run_suite._ReturnCodeComputer())
    290         collector._suite_views = [suite_job_view]
    291         collector._test_views = [suite_job_view, good_test, bad_test]
    292         collector._max_testname_width = max(
    293                 [len(v.get_testname()) for v in collector._test_views]) + 3
    294         collector._generate_web_and_buildbot_links()
    295         URL_PATTERN = run_suite._URL_PATTERN
    296         # expected_web_links is list of (anchor, url) tuples we
    297         # are expecting.
    298         expected_web_links = [
    299                  (v.get_testname(),
    300                   URL_PATTERN % ('http://fake_server',
    301                                  '%s-%s' % (v['afe_job_id'], 'chromeos-test')),
    302                   test_sponge_url)
    303                  for v in collector._test_views]
    304         # Verify web links are generated correctly.
    305         for i in range(len(collector._web_links)):
    306             expect = expected_web_links[i]
    307             self.assertEqual(collector._web_links[i].anchor, expect[0])
    308             self.assertEqual(collector._web_links[i].url, expect[1])
    309             self.assertEqual(collector._web_links[i].sponge_url, expect[2])
    310 
    311         expected_buildbot_links = [
    312                  (v.get_testname(),
    313                   URL_PATTERN % ('http://fake_server',
    314                                  '%s-%s' % (v['afe_job_id'], 'chromeos-test')))
    315                  for v in collector._test_views if v['status'] != 'GOOD']
    316         # Verify buildbot links are generated correctly.
    317         for i in range(len(collector.buildbot_links)):
    318             expect = expected_buildbot_links[i]
    319             self.assertEqual(collector.buildbot_links[i].anchor, expect[0])
    320             self.assertEqual(collector.buildbot_links[i].url, expect[1])
    321             self.assertEqual(collector.buildbot_links[i].retry_count, 0)
    322             # Assert that a retry dashboard link is created.
    323             self.assertNotEqual(
    324                     collector.buildbot_links[i].GenerateRetryLink(), '')
    325             self.assertNotEqual(
    326                     collector.buildbot_links[i].GenerateHistoryLink(), '')
    327 
    328 
    329     def _end_to_end_test_helper(
    330             self, include_bad_test=False, include_warn_test=False,
    331             include_timeout_test=False,
    332             include_self_aborted_test=False,
    333             include_aborted_by_suite_test=False,
    334             include_good_retry=False, include_bad_retry=False,
    335             include_good_test=True,
    336             suite_job_timed_out=False, suite_job_status='GOOD'):
    337         """A helper method for testing ResultCollector end-to-end.
    338 
    339         This method mocks the retrieving of required test views,
    340         and call ResultCollector.run() to collect the results.
    341 
    342         @param include_bad_test:
    343                 If True, include a view of a test which has status 'FAIL'.
    344         @param include_warn_test:
    345                 If True, include a view of a test which has status 'WARN'
    346         @param include_timeout_test:
    347                 If True, include a view of a test which was aborted before
    348                 started.
    349         @param include_self_aborted_test:
    350                 If True, include a view of test which was aborted after
    351                 started and hit hits own timeout.
    352         @param include_self_aborted_by_suite_test:
    353                 If True, include a view of test which was aborted after
    354                 started but has not hit its own timeout.
    355         @param include_good_retry:
    356                 If True, include a test that passed after retry.
    357         @param include_bad_retry:
    358                 If True, include a test that failed after retry.
    359         @param include_good_test:
    360                 If True, include a test that passed. If False, pretend no tests
    361                 (including the parent suite job) came back with any test
    362                 results.
    363         @param suite_job_status: One of 'GOOD' 'FAIL' 'ABORT' 'RUNNING'
    364 
    365         @returns: A ResultCollector instance.
    366         """
    367         suite_job_id = 100
    368         good_job_id = 101
    369         bad_job_id = 102
    370         warn_job_id = 102
    371         timeout_job_id = 100
    372         self_aborted_job_id = 104
    373         aborted_by_suite_job_id = 105
    374         good_retry_job_id = 106
    375         bad_retry_job_id = 107
    376         invalid_job_id_1 = 90
    377         invalid_job_id_2 = 91
    378         suite_name = 'dummy'
    379         build = 'lumpy-release/R27-3888.0.0'
    380         suite_job_keyvals = {
    381                 constants.DOWNLOAD_STARTED_TIME: '2014-04-29 13:14:20',
    382                 constants.PAYLOAD_FINISHED_TIME: '2014-04-29 13:14:25',
    383                 constants.ARTIFACT_FINISHED_TIME: '2014-04-29 13:14:30'}
    384 
    385         suite_job_started_time = '2014-04-29 13:14:37'
    386         if suite_job_timed_out:
    387             suite_job_keyvals['aborted_by'] = 'test_user'
    388             suite_job_finished_time = '2014-04-29 13:25:37'
    389             suite_job_status = 'ABORT'
    390         else:
    391             suite_job_finished_time = '2014-04-29 13:23:37'
    392 
    393         server_job_view = self._build_view(
    394                 10, 'SERVER_JOB', '----', suite_job_status, suite_job_id,
    395                 'lumpy-release/R27-3888.0.0-test_suites/control.dummy',
    396                 '', suite_job_keyvals, '2014-04-29 13:14:37',
    397                 '2014-04-29 13:20:27', job_started_time=suite_job_started_time,
    398                 job_finished_time=suite_job_finished_time)
    399         good_test = self._build_view(
    400                 11, 'dummy_Pass', '101-user/host/dummy_Pass', 'GOOD',
    401                 good_job_id, 'lumpy-release/R27-3888.0.0/dummy/dummy_Pass',
    402                 '', {}, '2014-04-29 13:15:35', '2014-04-29 13:15:36')
    403         bad_test = self._build_view(
    404                 12, 'dummy_Fail.Fail', '102-user/host/dummy_Fail.Fail', 'FAIL',
    405                 bad_job_id, 'lumpy-release/R27-3888.0.0/dummy/dummy_Fail.Fail',
    406                 'always fail', {}, '2014-04-29 13:16:00',
    407                 '2014-04-29 13:16:02')
    408         warn_test = self._build_view(
    409                 13, 'dummy_Fail.Warn', '102-user/host/dummy_Fail.Warn', 'WARN',
    410                 warn_job_id, 'lumpy-release/R27-3888.0.0/dummy/dummy_Fail.Warn',
    411                 'always warn', {}, '2014-04-29 13:16:00',
    412                 '2014-04-29 13:16:02')
    413         timeout_test = self._build_view(
    414                 15, 'dummy_Timeout', '', 'ABORT',
    415                 timeout_job_id,
    416                 'lumpy-release/R27-3888.0.0/dummy/dummy_Timeout',
    417                 'child job did not run', {}, '2014-04-29 13:15:37',
    418                 '2014-04-29 13:15:38')
    419         self_aborted_test = self._build_view(
    420                 16, 'dummy_Abort', '104-user/host/dummy_Abort', 'ABORT',
    421                 self_aborted_job_id,
    422                 'lumpy-release/R27-3888.0.0/dummy/dummy_Abort',
    423                 'child job aborted', {'aborted_by': 'test_user'},
    424                 '2014-04-29 13:15:39', '2014-04-29 13:15:40',
    425                 job_started_time='2014-04-29 13:15:39',
    426                 job_finished_time='2014-04-29 13:25:40')
    427         aborted_by_suite = self._build_view(
    428                 17, 'dummy_AbortBySuite', '105-user/host/dummy_AbortBySuite',
    429                 'RUNNING', aborted_by_suite_job_id,
    430                 'lumpy-release/R27-3888.0.0/dummy/dummy_Abort',
    431                 'aborted by suite', {'aborted_by': 'test_user'},
    432                 '2014-04-29 13:15:39', '2014-04-29 13:15:40',
    433                 job_started_time='2014-04-29 13:15:39',
    434                 job_finished_time='2014-04-29 13:15:40')
    435         good_retry = self._build_view(
    436                 18, 'dummy_RetryPass', '106-user/host/dummy_RetryPass', 'GOOD',
    437                 good_retry_job_id,
    438                 'lumpy-release/R27-3888.0.0/dummy/dummy_RetryPass',
    439                 '', {'retry_original_job_id': invalid_job_id_1},
    440                 '2014-04-29 13:15:37',
    441                 '2014-04-29 13:15:38', invalidates_test_idx=1)
    442         bad_retry = self._build_view(
    443                 19, 'dummy_RetryFail', '107-user/host/dummy_RetryFail', 'FAIL',
    444                 bad_retry_job_id,
    445                 'lumpy-release/R27-3888.0.0/dummy/dummy_RetryFail',
    446                 'retry failed', {'retry_original_job_id': invalid_job_id_2},
    447                 '2014-04-29 13:15:39', '2014-04-29 13:15:40',
    448                 invalidates_test_idx=2)
    449         invalid_test_1 = self._build_view(
    450                 1, 'dummy_RetryPass', '90-user/host/dummy_RetryPass', 'GOOD',
    451                 invalid_job_id_1,
    452                 'lumpy-release/R27-3888.0.0/dummy/dummy_RetryPass',
    453                 'original test failed', {}, '2014-04-29 13:10:00',
    454                 '2014-04-29 13:10:01')
    455         invalid_test_2 = self._build_view(
    456                 2, 'dummy_RetryFail', '91-user/host/dummy_RetryFail', 'FAIL',
    457                 invalid_job_id_2,
    458                 'lumpy-release/R27-3888.0.0/dummy/dummy_RetryFail',
    459                 'original test failed', {},
    460                 '2014-04-29 13:10:03', '2014-04-29 13:10:04')
    461 
    462         test_views = []
    463         child_jobs = set()
    464         missing_results = []
    465         if include_good_test:
    466             test_views.append(server_job_view)
    467             test_views.append(good_test)
    468             child_jobs.add(good_job_id)
    469         # Emulate missing even the parent/suite job.
    470         else:
    471             missing_results.append(suite_job_id)
    472         if include_bad_test:
    473             test_views.append(bad_test)
    474             child_jobs.add(bad_job_id)
    475         if include_warn_test:
    476             test_views.append(warn_test)
    477             child_jobs.add(warn_job_id)
    478         if include_timeout_test:
    479             test_views.append(timeout_test)
    480         if include_self_aborted_test:
    481             test_views.append(self_aborted_test)
    482             child_jobs.add(self_aborted_job_id)
    483         if include_good_retry:
    484             test_views.extend([good_retry, invalid_test_1])
    485             child_jobs.add(good_retry_job_id)
    486         if include_bad_retry:
    487             test_views.extend([bad_retry, invalid_test_2])
    488             child_jobs.add(bad_retry_job_id)
    489         if include_aborted_by_suite_test:
    490             test_views.append(aborted_by_suite)
    491             child_jobs.add(aborted_by_suite_job_id)
    492         self._mock_tko_get_detailed_test_views(test_views,
    493                missing_results=missing_results)
    494         self._mock_afe_get_jobs(suite_job_id, child_jobs)
    495         collector = run_suite.ResultCollector(
    496                'fake_server', self.afe, self.tko,
    497                'lumpy-release/R36-5788.0.0', 'dummy', suite_job_id,
    498                return_code_function=run_suite._ReturnCodeComputer())
    499         collector.run()
    500         collector.output_results()
    501         return collector
    502 
    503 
    504     def testEndToEndSuiteEmpty(self):
    505         """Test it returns code INFRA_FAILURE when no tests report back."""
    506         collector = self._end_to_end_test_helper(include_good_test=False)
    507         self.assertEqual(collector.return_result.return_code,
    508                          run_suite_common.RETURN_CODES.INFRA_FAILURE)
    509 
    510 
    511     def testEndToEndSuitePass(self):
    512         """Test it returns code OK when all test pass."""
    513         collector = self._end_to_end_test_helper()
    514         self.assertEqual(collector.return_result.return_code,
    515                          run_suite_common.RETURN_CODES.OK)
    516 
    517 
    518     def testEndToEndSuiteWarn(self):
    519         """Test it returns code WARNING when there is a test that warns."""
    520         collector = self._end_to_end_test_helper(include_warn_test=True)
    521         self.assertEqual(collector.return_result.return_code,
    522                          run_suite_common.RETURN_CODES.WARNING)
    523 
    524 
    525     def testEndToEndSuiteFail(self):
    526         """Test it returns code ERROR when there is a test that fails."""
    527         collector = self._end_to_end_test_helper(include_bad_test=True)
    528         self.assertEqual(collector.return_result.return_code,
    529                          run_suite_common.RETURN_CODES.ERROR)
    530 
    531 
    532     def testEndToEndSuiteJobFail(self):
    533         """Test it returns code SUITE_FAILURE when only the suite job failed."""
    534         collector = self._end_to_end_test_helper(suite_job_status='ABORT')
    535         self.assertEqual(
    536                 collector.return_result.return_code,
    537                 run_suite_common.RETURN_CODES.INFRA_FAILURE)
    538 
    539         collector = self._end_to_end_test_helper(suite_job_status='ERROR')
    540         self.assertEqual(
    541                 collector.return_result.return_code,
    542                 run_suite_common.RETURN_CODES.INFRA_FAILURE)
    543 
    544 
    545     def testEndToEndRetry(self):
    546         """Test it returns correct code when a test was retried."""
    547         collector = self._end_to_end_test_helper(include_good_retry=True)
    548         self.assertEqual(
    549                 collector.return_result.return_code,
    550                 run_suite_common.RETURN_CODES.WARNING)
    551 
    552         collector = self._end_to_end_test_helper(include_good_retry=True,
    553                 include_self_aborted_test=True)
    554         self.assertEqual(
    555                 collector.return_result.return_code,
    556                 run_suite_common.RETURN_CODES.ERROR)
    557 
    558         collector = self._end_to_end_test_helper(include_good_retry=True,
    559                 include_bad_test=True)
    560         self.assertEqual(
    561                 collector.return_result.return_code,
    562                 run_suite_common.RETURN_CODES.ERROR)
    563 
    564         collector = self._end_to_end_test_helper(include_bad_retry=True)
    565         self.assertEqual(
    566                 collector.return_result.return_code,
    567                 run_suite_common.RETURN_CODES.ERROR)
    568 
    569 
    570     def testEndToEndSuiteTimeout(self):
    571         """Test it returns correct code when a child job timed out."""
    572         # a child job timed out before started, none failed.
    573         collector = self._end_to_end_test_helper(include_timeout_test=True)
    574         self.assertEqual(
    575                 collector.return_result.return_code,
    576                 run_suite_common.RETURN_CODES.SUITE_TIMEOUT)
    577 
    578         # a child job timed out before started, and one test failed.
    579         collector = self._end_to_end_test_helper(
    580                 include_bad_test=True, include_timeout_test=True)
    581         self.assertEqual(collector.return_result.return_code,
    582                          run_suite_common.RETURN_CODES.ERROR)
    583 
    584         # a child job timed out before started, and one test warned.
    585         collector = self._end_to_end_test_helper(
    586                 include_warn_test=True, include_timeout_test=True)
    587         self.assertEqual(collector.return_result.return_code,
    588                          run_suite_common.RETURN_CODES.SUITE_TIMEOUT)
    589 
    590         # a child job timed out before started, and one test was retried.
    591         collector = self._end_to_end_test_helper(include_good_retry=True,
    592                 include_timeout_test=True)
    593         self.assertEqual(
    594                 collector.return_result.return_code,
    595                 run_suite_common.RETURN_CODES.SUITE_TIMEOUT)
    596 
    597         # a child jot was aborted because suite timed out.
    598         collector = self._end_to_end_test_helper(
    599                 include_aborted_by_suite_test=True)
    600         self.assertEqual(
    601                 collector.return_result.return_code,
    602                 run_suite_common.RETURN_CODES.SUITE_TIMEOUT)
    603 
    604         # suite job timed out.
    605         collector = self._end_to_end_test_helper(suite_job_timed_out=True)
    606         self.assertEqual(
    607                 collector.return_result.return_code,
    608                 run_suite_common.RETURN_CODES.SUITE_TIMEOUT)
    609 
    610 
    611 class LogLinkUnittests(unittest.TestCase):
    612     """Test the LogLink"""
    613 
    614     def testGenerateBuildbotLinks(self):
    615         """Test LogLink GenerateBuildbotLinks"""
    616         log_link_a = run_suite.LogLink('mock_anchor', 'mock_server',
    617                                       'mock_job_string',
    618                                       bug_info=('mock_bug_id', 1),
    619                                       reason='mock_reason',
    620                                       retry_count=1,
    621                                       testname='mock_testname')
    622         # Generate a bug link and a log link when bug_info is present
    623         self.assertTrue(len(list(log_link_a.GenerateBuildbotLinks())) == 2)
    624 
    625         log_link_b = run_suite.LogLink('mock_anchor', 'mock_server',
    626                                       'mock_job_string_b',
    627                                       reason='mock_reason',
    628                                       retry_count=1,
    629                                       testname='mock_testname')
    630         # Generate a log link when there is no bug_info
    631         self.assertTrue(len(list(log_link_b.GenerateBuildbotLinks())) == 1)
    632 
    633 
    634 class SimpleTimerUnittests(unittest.TestCase):
    635     """Test the simple timer."""
    636 
    637     def testPoll(self):
    638         """Test polling the timer."""
    639         interval_hours = 0.0001
    640         t = diagnosis_utils.SimpleTimer(interval_hours=interval_hours)
    641         deadline = t.deadline
    642         self.assertTrue(deadline is not None and
    643                         t.interval_hours == interval_hours)
    644         min_deadline = (datetime.now() +
    645                         datetime_base.timedelta(hours=interval_hours))
    646         time.sleep(interval_hours * 3600)
    647         self.assertTrue(t.poll())
    648         self.assertTrue(t.deadline >= min_deadline)
    649 
    650 
    651     def testBadInterval(self):
    652         """Test a bad interval."""
    653         t = diagnosis_utils.SimpleTimer(interval_hours=-1)
    654         self.assertTrue(t.deadline is None and t.poll() == False)
    655         t._reset()
    656         self.assertTrue(t.deadline is None and t.poll() == False)
    657 
    658 
    659 if __name__ == '__main__':
    660     unittest.main()
    661