blob: e1b62d07ebf4ebd30a36f3816b125a1ba6646f46 [file] [log] [blame]
#!/usr/bin/env python2
# Copyright 2019 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Module for buildbucket unittests."""
import json
import mock
import os
import unittest
import constants
import build_lib
import buildbucket
import task_executor
from chromite.api.gen.test_platform import request_pb2 as ctp_request
from chromite.api.gen.test_platform.suite_scheduler import analytics_pb2
from google.appengine.api import taskqueue
from google.appengine.ext import testbed
from google.protobuf import json_format
from infra_libs.buildbucket.proto import build_pb2
ADDR = u'http://localhost:1'
FAKE_UUID = 'c78e0bf3-4142-11ea-bc66-88e9fe4c5349'
FAKE_BUILD_ID = 8890493019851395280
def _get_suite_params(board='fake_board',
model='fake_model',
suite='fake_suite',
pool='MANAGED_POOL_QUOTA',
cros_build='fake_cros_build'):
return {
'suite': suite,
'board': board,
'model': model,
build_lib.BuildVersionKey.CROS_VERSION: cros_build,
build_lib.BuildVersionKey.FW_RW_VERSION: 'fake_firmware_rw_build',
build_lib.BuildVersionKey.FW_RO_VERSION: 'fake_firmware_ro_build',
build_lib.BuildVersionKey.ANDROID_BUILD_VERSION: 'fake_android_build',
build_lib.BuildVersionKey.TESTBED_BUILD_VERSION: 'fake_testbed_build',
'firmware_ro_version': 'fake_firmware_ro_build',
'num': 1,
'pool': pool,
'priority': 10,
'timeout': 12,
'timeout_mins': 4320,
'max_runtime_mins': 4320,
'no_wait_for_results': True,
'test_source_build': 'fake_test_source_build',
'job_retry': False,
'no_delay': False,
'force': False,
'run_prod_code': True,
'is_skylab': False,
}
class FakeTestPlatformClient(object):
def __init__(self, test):
self._test = test
self.called_with_requests = []
self.error = None
def ScheduleBuild(self, req, credentials=None, timeout=10):
self.called_with_requests.append(req)
if self.error:
return self.error
return build_pb2.Build(id=FAKE_BUILD_ID)
class FakeBigqueryRestClient(object):
def __init__(self, rest_client, project=None, dataset=None, table=None):
"""Initialize the mock class."""
self.table = table
self.rows = []
def insert(self, rows):
self.rows = rows
return True
class TestPlatformClientTestCase(unittest.TestCase):
def setUp(self):
super(TestPlatformClientTestCase, self).setUp()
self.fake_client = FakeTestPlatformClient(self)
patcher = mock.patch('buildbucket._get_client',
return_value=self._get_client(ADDR))
patcher.start()
self.client = buildbucket.TestPlatformClient(ADDR, 'foo-proj',
'foo-bucket', 'foo-builder')
self.addCleanup(patcher.stop)
self.testbed = testbed.Testbed()
self.testbed.activate()
self.addCleanup(self.testbed.deactivate)
self.testbed.init_taskqueue_stub(
root_path=os.path.join(os.path.dirname(__file__)))
self.taskqueue_stub = self.testbed.get_stub(testbed.TASKQUEUE_SERVICE_NAME)
self.queue = taskqueue.Queue(task_executor.SUITES_QUEUE)
_mock_application_id = mock.patch('constants.application_id')
self.mock_application_id = _mock_application_id.start()
self.addCleanup(_mock_application_id.stop)
self.mock_application_id.return_value = constants.AppID.PROD_APP
def testFormTestPlatformMultiRequestSuccessfully(self):
suite_kwargs = [_get_suite_params(board=b) for b in ['foo', 'goo']]
for suite in suite_kwargs:
task_executor.push(task_executor.SUITES_QUEUE, tag='fake_suite', **suite)
tasks = self.queue.lease_tasks_by_tag(
3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
self.client.multirequest_run(tasks, 'fake_suite')
self._assert_bb_client_called()
request_map = self._extract_request_map_from_bb_client()
request_list = [
_struct_to_ctp_request(v) for v in request_map.values()
]
request_list.sort(
key=lambda req: req.params.software_attributes.build_target.name)
for i, req in enumerate(request_list):
self.assertEqual(req.params.scheduling.priority, 10)
self.assertEqual(
req.params.scheduling.managed_pool,
ctp_request.Request.Params.Scheduling.MANAGED_POOL_QUOTA)
# Don't check for exact max timeout to stay DRY.
# But do check that the maximum is sane.
self.assertLess(req.params.time.maximum_duration.seconds,
2 * 24 * 60 * 60)
gs_url = ('gs://chromeos-image-archive/%s' %
suite_kwargs[i]['test_source_build'])
self.assertEqual(req.params.metadata.test_metadata_url, gs_url)
self.assertEqual(req.params.metadata.debug_symbols_archive_url, gs_url)
self.assertEqual(req.test_plan.suite[0].name, suite_kwargs[i]['suite'])
self.assertEqual(req.params.software_attributes.build_target.name,
suite_kwargs[i]['board'])
def testPoolName(self):
suite_kwargs = [_get_suite_params(board=b) for b in ['foo', 'goo', 'hoo']]
suite_kwargs[0]['pool'] = 'MANAGED_POOL_CTS'
suite_kwargs[1]['pool'] = 'wifi'
for suite in suite_kwargs:
task_executor.push(task_executor.SUITES_QUEUE, tag='some_suite', **suite)
tasks = self.queue.lease_tasks_by_tag(
3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
self.client.multirequest_run(tasks, 'some_suite')
self._assert_bb_client_called()
request_map = self._extract_request_map_from_bb_client()
request_list = [
_struct_to_ctp_request(v) for v in request_map.values()
]
self.assertEqual(len(request_list), 3)
request_list.sort(
key=lambda req: req.params.software_attributes.build_target.name)
self.assertEqual(request_list[0].params.scheduling.managed_pool,
ctp_request.Request.Params.Scheduling.MANAGED_POOL_CTS)
self.assertEqual(request_list[1].params.scheduling.unmanaged_pool,
suite_kwargs[1]['pool'])
def testNoFinalBuild(self):
suite_kwargs = _get_suite_params()
suite_kwargs[build_lib.BuildVersionKey.CROS_VERSION] = None
suite_kwargs[build_lib.BuildVersionKey.ANDROID_BUILD_VERSION] = None
suite_kwargs[build_lib.BuildVersionKey.TESTBED_BUILD_VERSION] = None
task_executor.push(task_executor.SUITES_QUEUE,
tag='fake_suite',
**suite_kwargs)
tasks = self.queue.lease_tasks_by_tag(
3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
executed = self.client.multirequest_run(tasks, 'fake_suite')
self.assertEqual(len(executed), 0)
self._assert_bb_client_not_called()
def testShouldSetQsAccountForUnmanagedPool(self):
suite_kwargs = _get_suite_params(pool='foo')
suite_kwargs['priority'] = '30'
suite_kwargs['qs_account'] = 'qs-foo'
task_executor.push(task_executor.SUITES_QUEUE,
tag='fake_suite',
**suite_kwargs)
tasks = self.queue.lease_tasks_by_tag(
3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
self.client.multirequest_run(tasks, 'fake_suite')
self._assert_bb_client_called()
request_map = self._extract_request_map_from_bb_client()
self.assertEqual(len(request_map), 1)
req = _struct_to_ctp_request(request_map['fake_board_fake_model'])
self.assertEqual(req.params.scheduling.qs_account, 'qs-foo')
# If qs_account is set, priority should be 0, the default value
# defined by proto3.
self.assertEqual(req.params.scheduling.priority, 0)
def testShouldSetQsAccountForManagedPool(self):
suite_kwargs = _get_suite_params(pool='MANAGED_POOL_QUOTA')
suite_kwargs['qs_account'] = 'qs-foo'
task_executor.push(task_executor.SUITES_QUEUE,
tag='fake_suite',
**suite_kwargs)
tasks = self.queue.lease_tasks_by_tag(
3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
self.client.multirequest_run(tasks, 'fake_suite')
self._assert_bb_client_called()
request_map = self._extract_request_map_from_bb_client()
self.assertEqual(len(request_map), 1)
req = _struct_to_ctp_request(request_map['fake_board_fake_model'])
self.assertEqual(req.params.scheduling.qs_account, 'qs-foo')
# If qs_account is set, priority should be 0, the default value
# defined by proto3.
self.assertEqual(req.params.scheduling.priority, 0)
def testShouldSetPriority(self):
suite_kwargs = _get_suite_params()
suite_kwargs['priority'] = '30'
task_executor.push(task_executor.SUITES_QUEUE,
tag='fake_suite',
**suite_kwargs)
tasks = self.queue.lease_tasks_by_tag(
3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
self.client.multirequest_run(tasks, 'fake_suite')
request_map = self._extract_request_map_from_bb_client()
self._assert_bb_client_called()
self.assertEqual(len(request_map), 1)
req = _struct_to_ctp_request(request_map['fake_board_fake_model'])
self.assertEqual(req.params.scheduling.priority, 30)
def testRequestWithInvalidTags(self):
suite_kwargs = [_get_suite_params(board=b) for b in ['foo', 'foo']]
# test request having None String Tag
suite_kwargs[0]['model'] = 'None'
# test request having None Tag
suite_kwargs[1]['model'] = None
for suite in suite_kwargs:
task_executor.push(task_executor.SUITES_QUEUE, tag='fake_suite', **suite)
tasks = self.queue.lease_tasks_by_tag(
3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
executed = self.client.multirequest_run(tasks, 'fake_suite')
self._assert_bb_client_called()
self.assertEqual(len(executed), 2)
request_map = self._extract_request_map_from_bb_client()
self.assertEqual(len(request_map), 2)
want = {
'suite:fake_suite',
'build:fake_cros_build',
'label-pool:MANAGED_POOL_QUOTA',
'label-board:foo',
}
want_bb = {
'suite:fake_suite',
'user_agent:suite_scheduler',
}
for req_name in ['foo', 'foo_1']:
req = _struct_to_ctp_request(request_map[req_name])
req_tags = set([t for t in req.params.decorations.tags])
self.assertEqual(want, req_tags)
bb_tags = set([t for t in self._extract_tags_from_bb_client()])
self.assertEqual(want_bb, bb_tags)
def testRequestTags(self):
suite_kwargs = _get_suite_params(
board='fake_board',
model='fake_model',
pool='DUT_POOL_QUOTA',
suite='fake_suite',
cros_build='fake_build',
)
task_executor.push(task_executor.SUITES_QUEUE,
tag='fake_suite',
**suite_kwargs)
tasks = self.queue.lease_tasks_by_tag(
3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
self.client.multirequest_run(tasks, 'fake_suite')
self._assert_bb_client_called()
want = {
'label-board:fake_board',
'label-model:fake_model',
'label-pool:DUT_POOL_QUOTA',
'suite:fake_suite',
'build:fake_build',
}
want_bb = {
'suite:fake_suite',
'user_agent:suite_scheduler',
}
request_map = self._extract_request_map_from_bb_client()
request_list = [
_struct_to_ctp_request(v) for v in request_map.values()
]
self.assertEqual(len(request_list), 1)
req = request_list[0]
req_tags = set([t for t in req.params.decorations.tags])
self.assertEqual(want, req_tags)
bb_tags = set([t for t in self._extract_tags_from_bb_client()])
self.assertEqual(want_bb, bb_tags)
def testRequestUserDefinedDimensions(self):
suite_kwargs = _get_suite_params(
board='fake_board',
model='fake_model',
pool='MANAGED_POOL_QUOTA',
suite='fake_suite',
cros_build='fake_build',
)
suite_kwargs['dimensions'] = 'label-wifi:foo,label-bt:hoo'
task_executor.push(task_executor.SUITES_QUEUE,
tag='fake_suite',
**suite_kwargs)
tasks = self.queue.lease_tasks_by_tag(
3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
self.client.multirequest_run(tasks, 'fake_suite')
self._assert_bb_client_called()
request_map = self._extract_request_map_from_bb_client()
request_list = [
_struct_to_ctp_request(v) for v in request_map.values()
]
self.assertEqual(len(request_list), 1)
req = request_list[0]
free_dimensions = req.params.freeform_attributes.swarming_dimensions
self.assertTrue(len(free_dimensions), 2)
self.assertTrue('label-wifi:foo' in free_dimensions)
self.assertTrue('label-bt:hoo' in free_dimensions)
def testRequestInvalidUserDefinedDimensions(self):
suite_kwargs = _get_suite_params(
board='fake_board',
model='fake_model',
pool='MANAGED_POOL_QUOTA',
suite='fake_suite',
cros_build='fake_build',
)
suite_kwargs['dimensions'] = 'invalid-dimension'
task_executor.push(task_executor.SUITES_QUEUE,
tag='fake_suite',
**suite_kwargs)
tasks = self.queue.lease_tasks_by_tag(
3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
self.client.multirequest_run(tasks, 'fake_suite')
self._assert_bb_client_not_called()
def _get_client(self, addr):
self.assertEqual(ADDR, addr)
return self.fake_client
def _assert_bb_client_called(self, times=1):
actual = len(self.fake_client.called_with_requests)
self.assertEqual(actual, times,
"BB client called %s times, expected %s" % (actual, times))
def _assert_bb_client_not_called(self):
actual = len(self.fake_client.called_with_requests)
self.assertEqual(actual, 0,
"BB client called %s times, expected 0"% actual)
def _extract_request_map_from_bb_client(self):
return self.fake_client.called_with_requests[0].properties['requests']
def _extract_tags_from_bb_client(self):
return [
'%s:%s' % (t.key, t.value)
for t in self.fake_client.called_with_requests[0].tags
]
class TestTaskExecutions(TestPlatformClientTestCase):
def setUp(self):
super(TestTaskExecutions, self).setUp()
_mock_bq_client = mock.patch('rest_client.BigqueryRestClient')
mock_bq_client = _mock_bq_client.start()
self.addCleanup(_mock_bq_client.stop)
self.mock_bq_client = FakeBigqueryRestClient(None,
project='proj',
dataset='dataset',
table='foo')
mock_bq_client.return_value = self.mock_bq_client
def testRecordSuccessfulTaskExecution(self):
suite = _get_suite_params(board='fake_build', model='fake_model')
suite['task_id'] = FAKE_UUID
task_executor.push(task_executor.SUITES_QUEUE, tag='fake_suite', **suite)
tasks = self.queue.lease_tasks_by_tag(
3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
self.client.multirequest_run(tasks, 'fake_suite')
self._assert_bb_client_called()
task_execution = json_format.Parse(
json.dumps(self.mock_bq_client.rows[0]['json']),
analytics_pb2.ExecutionTask())
self.assertEqual(task_execution.queued_task_id, FAKE_UUID)
self.assertEqual(task_execution.request_tag, 'fake_build_fake_model')
self.assertEqual(task_execution.response.ctp_build_id, str(FAKE_BUILD_ID))
self.assertEqual(task_execution.error.error_message, '')
def testRecordFailedTaskExecution(self):
suite = _get_suite_params(board='fake_build', model='fake_model')
suite['task_id'] = FAKE_UUID
task_executor.push(task_executor.SUITES_QUEUE, tag='fake_suite', **suite)
self.fake_client.error = "cros_test_platform error"
tasks = self.queue.lease_tasks_by_tag(
3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
self.client.multirequest_run(tasks, 'fake_suite')
self._assert_bb_client_called()
task_execution = json_format.Parse(
json.dumps(self.mock_bq_client.rows[0]['json']),
analytics_pb2.ExecutionTask())
self.assertEqual(task_execution.queued_task_id, FAKE_UUID)
self.assertEqual(task_execution.request_tag, 'fake_build_fake_model')
self.assertEqual(task_execution.response.ctp_build_id, '')
self.assertEqual(task_execution.error.error_message,
self.fake_client.error)
def testIgnoreTaskWithoutTaskID(self):
suite = _get_suite_params(board='foo')
task_executor.push(task_executor.SUITES_QUEUE, tag='fake_suite', **suite)
self.fake_client.error = "cros_test_platform error"
tasks = self.queue.lease_tasks_by_tag(
3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
self.client.multirequest_run(tasks, 'fake_suite')
self._assert_bb_client_called()
self.assertEqual(len(self.mock_bq_client.rows), 0)
def testIgnoreSuiteForUnmanagedPoolInStaging(self):
self.mock_application_id.return_value = 'suite-scheduler-staging'
suite = _get_suite_params(pool='foo')
task_executor.push(task_executor.SUITES_QUEUE, tag='fake_suite', **suite)
self.fake_client.error = "cros_test_platform error"
tasks = self.queue.lease_tasks_by_tag(
3600, constants.Buildbucket.MULTIREQUEST_SIZE, deadline=60)
self.client.multirequest_run(tasks, 'fake_suite')
self._assert_bb_client_not_called()
def _struct_to_ctp_request(struct_pb2):
"""Transform google struct proto to test_platform_request.
Args:
struct_pb2: A struct_pb2 instance.
Returns:
A ctp_request instance.
"""
json = json_format.MessageToJson(struct_pb2)
return json_format.Parse(json, ctp_request.Request())