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