| OLD | NEW |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import collections | 5 import collections |
| 6 import json | 6 import json |
| 7 import mock |
| 7 import os | 8 import os |
| 8 import urllib | 9 import urllib |
| 9 import zlib | 10 import zlib |
| 10 | 11 |
| 12 from google.appengine.api.urlfetch_errors import ( |
| 13 DeadlineExceededError, DownloadError, ConnectionClosedError) |
| 14 |
| 15 from common.http_client_appengine import HttpClientAppengine as HttpClient |
| 11 from common.retry_http_client import RetryHttpClient | 16 from common.retry_http_client import RetryHttpClient |
| 12 from model.wf_config import FinditConfig | 17 from model.wf_config import FinditConfig |
| 13 from model.wf_step import WfStep | 18 from model.wf_step import WfStep |
| 14 from waterfall import swarming_util | 19 from waterfall import swarming_util |
| 15 from waterfall import waterfall_config | 20 from waterfall import waterfall_config |
| 16 from waterfall.swarming_task_request import SwarmingTaskRequest | 21 from waterfall.swarming_task_request import SwarmingTaskRequest |
| 17 from waterfall.test import wf_testcase | 22 from waterfall.test import wf_testcase |
| 18 | 23 |
| 19 | 24 |
| 20 class SwarmingHttpClient(RetryHttpClient): | 25 class SwarmingHttpClient(RetryHttpClient): |
| (...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 158 url = ('https://chromium-swarm.appspot.com/' | 163 url = ('https://chromium-swarm.appspot.com/' |
| 159 '_ah/api/swarming/v1/task/%s/request' % task_id) | 164 '_ah/api/swarming/v1/task/%s/request' % task_id) |
| 160 self.logged_http_client.SetResponse( | 165 self.logged_http_client.SetResponse( |
| 161 'get', url, json.dumps(task_request_json), 200) | 166 'get', url, json.dumps(task_request_json), 200) |
| 162 | 167 |
| 163 task_request = swarming_util.GetSwarmingTaskRequest( | 168 task_request = swarming_util.GetSwarmingTaskRequest( |
| 164 task_id, self.logged_http_client) | 169 task_id, self.logged_http_client) |
| 165 | 170 |
| 166 self.assertEqual(task_request_json, task_request.Serialize()) | 171 self.assertEqual(task_request_json, task_request.Serialize()) |
| 167 | 172 |
| 173 @mock.patch.object(swarming_util, '_SendRequestToServer', |
| 174 return_value=(None, {'code': 1, 'message': 'error'})) |
| 175 def testGetSwarmingTaskRequestError(self, _): |
| 176 self.assertIsNone( |
| 177 swarming_util.GetSwarmingTaskRequest('task_id1', HttpClient())) |
| 178 |
| 168 def testTriggerSwarmingTask(self): | 179 def testTriggerSwarmingTask(self): |
| 169 request = SwarmingTaskRequest() | 180 request = SwarmingTaskRequest() |
| 170 request.expiration_secs = 2 | 181 request.expiration_secs = 2 |
| 171 request.name = 'name' | 182 request.name = 'name' |
| 172 request.parent_task_id = 'pti' | 183 request.parent_task_id = 'pti' |
| 173 request.priority = 1 | 184 request.priority = 1 |
| 174 request.tags = ['tag'] | 185 request.tags = ['tag'] |
| 175 request.user = 'user' | 186 request.user = 'user' |
| 176 request.command = 'cmd' | 187 request.command = 'cmd' |
| 177 request.dimensions = [{'key': 'd', 'value': 'dv'}] | 188 request.dimensions = [{'key': 'd', 'value': 'dv'}] |
| (...skipping 22 matching lines...) Expand all Loading... |
| 200 'extra_args': ['--flag'], | 211 'extra_args': ['--flag'], |
| 201 'grace_period_secs': 5, | 212 'grace_period_secs': 5, |
| 202 'idempotent': True, | 213 'idempotent': True, |
| 203 'inputs_ref': {'isolated': 'i'}, | 214 'inputs_ref': {'isolated': 'i'}, |
| 204 'io_timeout_secs': 3, | 215 'io_timeout_secs': 3, |
| 205 }, | 216 }, |
| 206 'tags': ['tag', 'findit:1', 'project:Chromium', 'purpose:post-commit'], | 217 'tags': ['tag', 'findit:1', 'project:Chromium', 'purpose:post-commit'], |
| 207 'user': 'user', | 218 'user': 'user', |
| 208 } | 219 } |
| 209 | 220 |
| 210 task_id = swarming_util.TriggerSwarmingTask( | 221 task_id, error = swarming_util.TriggerSwarmingTask( |
| 211 request, self.logged_http_client) | 222 request, self.logged_http_client) |
| 212 self.assertEqual('1', task_id) | 223 self.assertEqual('1', task_id) |
| 224 self.assertIsNone(error) |
| 213 | 225 |
| 214 method, data, _ = self.logged_http_client.GetRequest(url) | 226 method, data, _ = self.logged_http_client.GetRequest(url) |
| 215 self.assertEqual('post', method) | 227 self.assertEqual('post', method) |
| 216 self.assertEqual(expected_task_request_json, json.loads(data)) | 228 self.assertEqual(expected_task_request_json, json.loads(data)) |
| 217 | 229 |
| 230 @mock.patch.object(swarming_util, '_SendRequestToServer', |
| 231 return_value=(None, {'code': 1, 'message': 'error'})) |
| 232 def testTriggerSwarmingTaskError(self, _): |
| 233 request = SwarmingTaskRequest() |
| 234 task_id, error = swarming_util.TriggerSwarmingTask( |
| 235 request, HttpClient()) |
| 236 self.assertIsNone(task_id) |
| 237 self.assertIsNotNone(error) |
| 238 |
| 218 def testGetIsolatedDataForFailedBuild(self): | 239 def testGetIsolatedDataForFailedBuild(self): |
| 219 master_name = 'm' | 240 master_name = 'm' |
| 220 builder_name = 'b' | 241 builder_name = 'b' |
| 221 build_number = 223 | 242 build_number = 223 |
| 222 failed_steps = { | 243 failed_steps = { |
| 223 'a_tests': { | 244 'a_tests': { |
| 224 'current_failure': 2, | 245 'current_failure': 2, |
| 225 'first_failure': 0 | 246 'first_failure': 0 |
| 226 }, | 247 }, |
| 227 'unit_tests': { | 248 'unit_tests': { |
| (...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 368 'isolatedserver': waterfall_config.GetSwarmingSettings().get( | 389 'isolatedserver': waterfall_config.GetSwarmingSettings().get( |
| 369 'isolated_server') | 390 'isolated_server') |
| 370 } | 391 } |
| 371 isolated_storage_url = waterfall_config.GetSwarmingSettings().get( | 392 isolated_storage_url = waterfall_config.GetSwarmingSettings().get( |
| 372 'isolated_storage_url') | 393 'isolated_storage_url') |
| 373 self.http_client._SetResponseForPostRequest('shard1_isolated') | 394 self.http_client._SetResponseForPostRequest('shard1_isolated') |
| 374 self.http_client._SetResponseForPostRequest('shard1_url') | 395 self.http_client._SetResponseForPostRequest('shard1_url') |
| 375 self.http_client._SetResponseForGetRequestIsolated( | 396 self.http_client._SetResponseForGetRequestIsolated( |
| 376 'https://%s/default-gzip/shard1' % isolated_storage_url, 'shard1') | 397 'https://%s/default-gzip/shard1' % isolated_storage_url, 'shard1') |
| 377 | 398 |
| 378 result = swarming_util._DownloadTestResults( | 399 result, error = swarming_util._DownloadTestResults( |
| 379 isolated_data, self.http_client) | 400 isolated_data, self.http_client) |
| 380 | 401 |
| 381 expected_result = json.loads(zlib.decompress( | 402 expected_result = json.loads(zlib.decompress( |
| 382 self.http_client._GetData('isolated', 'shard1'))) | 403 self.http_client._GetData('isolated', 'shard1'))) |
| 383 self.assertEqual(expected_result, result) | 404 self.assertEqual(expected_result, result) |
| 405 self.assertIsNone(error) |
| 384 | 406 |
| 385 def testDownloadTestResultsFailedForSecondHash(self): | 407 def testDownloadTestResultsFailedForSecondHash(self): |
| 386 isolated_data = { | 408 isolated_data = { |
| 387 'digest': 'not found', | 409 'digest': 'not found', |
| 388 'namespace': 'default-gzip', | 410 'namespace': 'default-gzip', |
| 389 'isolatedserver': waterfall_config.GetSwarmingSettings().get( | 411 'isolatedserver': waterfall_config.GetSwarmingSettings().get( |
| 390 'isolated_server') | 412 'isolated_server') |
| 391 } | 413 } |
| 392 | 414 |
| 393 result = swarming_util._DownloadTestResults( | 415 result, error = swarming_util._DownloadTestResults( |
| 394 isolated_data, self.http_client) | 416 isolated_data, self.http_client) |
| 395 | 417 |
| 396 self.assertIsNone(result) | 418 self.assertIsNone(result) |
| 419 self.assertIsNotNone(error) |
| 397 | 420 |
| 398 def testDownloadTestResultsFailedForParsingSecondHash(self): | 421 def testDownloadTestResultsFailedForParsingSecondHash(self): |
| 399 isolated_data = { | 422 isolated_data = { |
| 400 'digest': 'not found', | 423 'digest': 'not found', |
| 401 'namespace': 'default-gzip', | 424 'namespace': 'default-gzip', |
| 402 'isolatedserver': waterfall_config.GetSwarmingSettings().get( | 425 'isolatedserver': waterfall_config.GetSwarmingSettings().get( |
| 403 'isolated_server') | 426 'isolated_server') |
| 404 } | 427 } |
| 405 | 428 |
| 406 self.http_client._SetResponseForPostRequest('not found') | 429 self.http_client._SetResponseForPostRequest('not found') |
| 407 result = swarming_util._DownloadTestResults( | 430 result, error = swarming_util._DownloadTestResults( |
| 408 isolated_data, self.http_client) | 431 isolated_data, self.http_client) |
| 409 | 432 |
| 410 self.assertIsNone(result) | 433 self.assertIsNone(result) |
| 434 self.assertIsNone(error) |
| 411 | 435 |
| 412 def testDownloadTestResultsFailedForFileUrl(self): | 436 def testDownloadTestResultsFailedForFileUrl(self): |
| 413 isolated_data = { | 437 isolated_data = { |
| 414 'digest': 'shard1_isolated', | 438 'digest': 'shard1_isolated', |
| 415 'namespace': 'default-gzip', | 439 'namespace': 'default-gzip', |
| 416 'isolatedserver': waterfall_config.GetSwarmingSettings().get( | 440 'isolatedserver': waterfall_config.GetSwarmingSettings().get( |
| 417 'isolated_server') | 441 'isolated_server') |
| 418 } | 442 } |
| 419 self.http_client._SetResponseForPostRequest('shard1_isolated') | 443 self.http_client._SetResponseForPostRequest('shard1_isolated') |
| 420 result = swarming_util._DownloadTestResults( | 444 result, error = swarming_util._DownloadTestResults( |
| 421 isolated_data, self.http_client) | 445 isolated_data, self.http_client) |
| 422 | 446 |
| 423 self.assertIsNone(result) | 447 self.assertIsNone(result) |
| 448 self.assertIsNotNone(error) |
| 424 | 449 |
| 425 def testDownloadTestResultsFailedForFile(self): | 450 def testDownloadTestResultsFailedForFile(self): |
| 426 isolated_data = { | 451 isolated_data = { |
| 427 'digest': 'shard1_isolated', | 452 'digest': 'shard1_isolated', |
| 428 'namespace': 'default-gzip', | 453 'namespace': 'default-gzip', |
| 429 'isolatedserver': waterfall_config.GetSwarmingSettings().get( | 454 'isolatedserver': waterfall_config.GetSwarmingSettings().get( |
| 430 'isolated_server') | 455 'isolated_server') |
| 431 } | 456 } |
| 432 self.http_client._SetResponseForPostRequest('shard1_isolated') | 457 self.http_client._SetResponseForPostRequest('shard1_isolated') |
| 433 self.http_client._SetResponseForPostRequest('shard1_url') | 458 self.http_client._SetResponseForPostRequest('shard1_url') |
| 434 result = swarming_util._DownloadTestResults( | 459 result, error = swarming_util._DownloadTestResults( |
| 435 isolated_data, self.http_client) | 460 isolated_data, self.http_client) |
| 436 | 461 |
| 437 self.assertIsNone(result) | 462 self.assertIsNone(result) |
| 463 self.assertIsNone(error) |
| 438 | 464 |
| 439 def testRetrieveShardedTestResultsFromIsolatedServer(self): | 465 def testRetrieveShardedTestResultsFromIsolatedServer(self): |
| 440 isolated_data = [ | 466 isolated_data = [ |
| 441 { | 467 { |
| 442 'digest': 'shard1_isolated', | 468 'digest': 'shard1_isolated', |
| 443 'namespace': 'default-gzip', | 469 'namespace': 'default-gzip', |
| 444 'isolatedserver': waterfall_config.GetSwarmingSettings().get( | 470 'isolatedserver': waterfall_config.GetSwarmingSettings().get( |
| 445 'isolated_server') | 471 'isolated_server') |
| 446 }, | 472 }, |
| 447 { | 473 { |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 517 result = swarming_util.RetrieveShardedTestResultsFromIsolatedServer( | 543 result = swarming_util.RetrieveShardedTestResultsFromIsolatedServer( |
| 518 isolated_data, self.http_client) | 544 isolated_data, self.http_client) |
| 519 | 545 |
| 520 self.assertIsNone(result) | 546 self.assertIsNone(result) |
| 521 | 547 |
| 522 def testGetSwarmingTaskResultById(self): | 548 def testGetSwarmingTaskResultById(self): |
| 523 task_id = '2944afa502297110' | 549 task_id = '2944afa502297110' |
| 524 | 550 |
| 525 self.http_client._SetResponseForGetRequestSwarmingResult(task_id) | 551 self.http_client._SetResponseForGetRequestSwarmingResult(task_id) |
| 526 | 552 |
| 527 data = swarming_util.GetSwarmingTaskResultById( | 553 data, error = swarming_util.GetSwarmingTaskResultById( |
| 528 task_id, self.http_client) | 554 task_id, self.http_client) |
| 529 | 555 |
| 530 expected_outputs_ref = { | 556 expected_outputs_ref = { |
| 531 'isolatedserver': waterfall_config.GetSwarmingSettings().get( | 557 'isolatedserver': waterfall_config.GetSwarmingSettings().get( |
| 532 'isolated_server'), | 558 'isolated_server'), |
| 533 'namespace': 'default-gzip', | 559 'namespace': 'default-gzip', |
| 534 'isolated': 'shard1_isolated' | 560 'isolated': 'shard1_isolated' |
| 535 } | 561 } |
| 536 | 562 |
| 537 self.assertEqual('COMPLETED', data['state']) | 563 self.assertEqual('COMPLETED', data['state']) |
| 538 self.assertEqual(expected_outputs_ref, data['outputs_ref']) | 564 self.assertEqual(expected_outputs_ref, data['outputs_ref']) |
| 565 self.assertIsNone(error) |
| 566 |
| 567 @mock.patch.object(swarming_util, '_SendRequestToServer', |
| 568 return_value=(None, {'code': 1, 'message': 'error'})) |
| 569 def testGetSwarmingTaskResultByIdError(self, _): |
| 570 data, error = swarming_util.GetSwarmingTaskResultById( |
| 571 'task_id', HttpClient()) |
| 572 self.assertEqual({}, data) |
| 573 self.assertIsNotNone(error) |
| 539 | 574 |
| 540 def testGetSwarmingTaskFailureLog(self): | 575 def testGetSwarmingTaskFailureLog(self): |
| 541 outputs_ref = { | 576 outputs_ref = { |
| 542 'isolatedserver': waterfall_config.GetSwarmingSettings().get( | 577 'isolatedserver': waterfall_config.GetSwarmingSettings().get( |
| 543 'isolated_server'), | 578 'isolated_server'), |
| 544 'namespace': 'default-gzip', | 579 'namespace': 'default-gzip', |
| 545 'isolated': 'shard1_isolated' | 580 'isolated': 'shard1_isolated' |
| 546 } | 581 } |
| 547 | 582 |
| 548 self.http_client._SetResponseForPostRequest('shard1_isolated') | 583 self.http_client._SetResponseForPostRequest('shard1_isolated') |
| 549 self.http_client._SetResponseForPostRequest('shard1_url') | 584 self.http_client._SetResponseForPostRequest('shard1_url') |
| 550 self.http_client._SetResponseForGetRequestIsolated( | 585 self.http_client._SetResponseForGetRequestIsolated( |
| 551 'https://%s/default-gzip/shard1' % ( | 586 'https://%s/default-gzip/shard1' % ( |
| 552 waterfall_config.GetSwarmingSettings().get('isolated_storage_url')), | 587 waterfall_config.GetSwarmingSettings().get('isolated_storage_url')), |
| 553 'shard1') | 588 'shard1') |
| 554 | 589 |
| 555 result = swarming_util.GetSwarmingTaskFailureLog( | 590 result, error = swarming_util.GetSwarmingTaskFailureLog( |
| 556 outputs_ref, self.http_client) | 591 outputs_ref, self.http_client) |
| 557 | 592 |
| 558 expected_result = json.loads(zlib.decompress( | 593 expected_result = json.loads(zlib.decompress( |
| 559 self.http_client._GetData('isolated', 'shard1'))) | 594 self.http_client._GetData('isolated', 'shard1'))) |
| 560 self.assertEqual(expected_result, result) | 595 self.assertEqual(expected_result, result) |
| 596 self.assertIsNone(error) |
| 561 | 597 |
| 562 def testRetrieveOutputJsonFileGetDirectly(self): | 598 def testRetrieveOutputJsonFileGetDirectly(self): |
| 563 output_json_content = ('{"content": "eJyrVkpLzMwpLUotVrKKVgpJLS4xV' | 599 output_json_content = ('{"content": "eJyrVkpLzMwpLUotVrKKVgpJLS4xV' |
| 564 'IrVUVAqS8zJTFGyUigpKk2tBQDr9wxZ"}') | 600 'IrVUVAqS8zJTFGyUigpKk2tBQDr9wxZ"}') |
| 565 | 601 |
| 566 failure_log = swarming_util._RetrieveOutputJsonFile( | 602 failure_log = swarming_util._RetrieveOutputJsonFile( |
| 567 output_json_content, self.http_client) | 603 output_json_content, self.http_client) |
| 568 | 604 |
| 569 expected_failure_log = { | 605 expected_failure_log = { |
| 570 'failures': ['Test1'], | 606 'failures': ['Test1'], |
| 571 'valid': True | 607 'valid': True |
| 572 } | 608 } |
| 573 | 609 |
| 574 self.assertEqual(expected_failure_log, failure_log) | 610 self.assertEqual(expected_failure_log, failure_log) |
| 575 | 611 |
| 576 def testGetTagValueInvalidTag(self): | 612 def testGetTagValueInvalidTag(self): |
| 577 tags = ['a:1', 'b:2'] | 613 tags = ['a:1', 'b:2'] |
| 578 self.assertIsNone(swarming_util.GetTagValue(tags, 'c')) | 614 self.assertIsNone(swarming_util.GetTagValue(tags, 'c')) |
| 579 | 615 |
| 580 def testGenerateIsolatedDataOutputsrefNone(self): | 616 def testGenerateIsolatedDataOutputsrefNone(self): |
| 581 self.assertEqual({}, swarming_util._GenerateIsolatedData(None)) | 617 self.assertEqual({}, swarming_util._GenerateIsolatedData(None)) |
| 582 | 618 |
| 583 def testFetchOutputJsonInfoFromIsolatedServerReturnNone(self): | 619 def testFetchOutputJsonInfoFromIsolatedServerReturnNone(self): |
| 584 self.assertIsNone(swarming_util._FetchOutputJsonInfoFromIsolatedServer( | 620 self.assertIsNone(swarming_util._FetchOutputJsonInfoFromIsolatedServer( |
| 585 None, self.http_client)) | 621 None, self.http_client)) |
| 622 |
| 623 @mock.patch.object( |
| 624 RetryHttpClient, 'Get', side_effect=ConnectionClosedError()) |
| 625 def testSendRequestToServerConnectionClosedError(self, _): |
| 626 content, error = swarming_util._SendRequestToServer('url', HttpClient()) |
| 627 self.assertIsNone(content) |
| 628 self.assertEqual( |
| 629 error['code'], swarming_util.URLFETCH_CONNECTION_CLOSED_ERROR) |
| 630 |
| 631 @mock.patch.object( |
| 632 RetryHttpClient, 'Get', side_effect=DeadlineExceededError()) |
| 633 def testSendRequestToServerDeadlineExceededError(self, _): |
| 634 content, error = swarming_util._SendRequestToServer('url', HttpClient()) |
| 635 self.assertIsNone(content) |
| 636 self.assertEqual( |
| 637 error['code'], swarming_util.URLFETCH_DEADLINE_EXCEEDED_ERROR) |
| 638 |
| 639 @mock.patch.object(RetryHttpClient, 'Get', side_effect=DownloadError()) |
| 640 def testSendRequestToServerDownloadError(self, _): |
| 641 content, error = swarming_util._SendRequestToServer('url', HttpClient()) |
| 642 self.assertIsNone(content) |
| 643 self.assertEqual(error['code'], swarming_util.URLFETCH_DOWNLOAD_ERROR) |
| OLD | NEW |