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 6import collections 7import datetime as datetime_base 8from datetime import datetime 9import mock 10import time 11import unittest 12 13import common 14 15from autotest_lib.server.cros.dynamic_suite import constants 16from autotest_lib.site_utils import run_suite 17from autotest_lib.site_utils import run_suite_common 18from autotest_lib.site_utils import diagnosis_utils 19 20 21class 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 56class 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 611class 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 634class 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 659if __name__ == '__main__': 660 unittest.main() 661