Best Python code snippet using autotest_python
service_test.py
Source:service_test.py
1# Copyright 2014 The Chromium Authors. All rights reserved.2# Use of this source code is governed by a BSD-style license that can be3# found in the LICENSE file.4import contextlib5import datetime6from components import auth7from components import net8from components import utils9from google.appengine.ext import ndb10from testing_utils import testing11import mock12from test import future13import acl14import errors15import model16import notifications17import service18import swarming19class BuildBucketServiceTest(testing.AppengineTestCase):20 def __init__(self, *args, **kwargs):21 super(BuildBucketServiceTest, self).__init__(*args, **kwargs)22 self.test_build = None23 def mock_cannot(self, action):24 def can_async(_bucket, requested_action, _identity=None):25 return future(action != requested_action)26 self.mock(acl, 'can_async', can_async)27 def setUp(self):28 super(BuildBucketServiceTest, self).setUp()29 self.test_build = model.Build(30 bucket='chromium',31 parameters={32 'buildername': 'infra',33 'changes': [{34 'author': 'nodir@google.com',35 'message': 'buildbucket: initial commit'36 }]37 }38 )39 self.current_identity = auth.Identity('service', 'unittest')40 self.mock(auth, 'get_current_identity', lambda: self.current_identity)41 self.mock(acl, 'can_async', lambda *_: future(True))42 self.now = datetime.datetime(2015, 1, 1)43 self.mock(utils, 'utcnow', lambda: self.now)44 self.mock(swarming, 'is_for_swarming_async', mock.Mock())45 self.mock(swarming, 'create_task_async', mock.Mock())46 swarming.is_for_swarming_async.return_value = ndb.Future()47 swarming.is_for_swarming_async.return_value.set_result(False)48 def put_many_builds(self):49 for _ in xrange(100):50 b = model.Build(bucket=self.test_build.bucket)51 b.put()52 #################################### ADD #####################################53 def test_add(self):54 params = {'buildername': 'linux_rel'}55 build = service.add(56 bucket='chromium',57 parameters=params,58 )59 self.assertIsNotNone(build.key)60 self.assertIsNotNone(build.key.id())61 self.assertEqual(build.bucket, 'chromium')62 self.assertEqual(build.parameters, params)63 self.assertEqual(build.created_by, auth.get_current_identity())64 def test_add_with_client_operation_id(self):65 build = service.add(66 bucket='chromium',67 parameters={'buildername': 'linux_rel'},68 client_operation_id='1',69 )70 build2 = service.add(71 bucket='chromium',72 parameters={'buildername': 'linux_rel'},73 client_operation_id='1',74 )75 self.assertIsNotNone(build.key)76 self.assertEqual(build, build2)77 def test_add_with_bad_bucket_name(self):78 with self.assertRaises(errors.InvalidInputError):79 service.add(bucket='chromium as')80 with self.assertRaises(errors.InvalidInputError):81 service.add(bucket='')82 def test_add_with_leasing(self):83 build = service.add(84 bucket='chromium',85 lease_expiration_date=utils.utcnow() + datetime.timedelta(seconds=10),86 )87 self.assertTrue(build.is_leased)88 self.assertGreater(build.lease_expiration_date, utils.utcnow())89 self.assertIsNotNone(build.lease_key)90 def test_add_with_auth_error(self):91 self.mock_cannot(acl.Action.ADD_BUILD)92 with self.assertRaises(auth.AuthorizationError):93 service.add(self.test_build.bucket)94 def test_add_with_bad_parameters(self):95 with self.assertRaises(errors.InvalidInputError):96 service.add('bucket', parameters=[])97 def test_add_with_swarming_400(self):98 swarming.is_for_swarming_async.return_value = ndb.Future()99 swarming.is_for_swarming_async.return_value.set_result(True)100 swarming.create_task_async.side_effect = net.Error(101 '', status_code=400, response='bad request')102 with self.assertRaises(errors.InvalidInputError):103 service.add(self.test_build.bucket)104 def test_add_with_swarming_403(self):105 swarming.is_for_swarming_async.return_value = ndb.Future()106 swarming.is_for_swarming_async.return_value.set_result(True)107 swarming.create_task_async.side_effect = net.AuthError(108 '', status_code=403, response='access denied')109 with self.assertRaises(auth.AuthorizationError):110 service.add(self.test_build.bucket)111 ################################### RETRY ####################################112 def test_retry(self):113 self.test_build.put()114 build = service.retry(self.test_build.key.id())115 self.assertIsNotNone(build)116 self.assertIsNotNone(build.key)117 self.assertNotEqual(build.key.id(), self.test_build.key.id())118 self.assertEqual(build.bucket, self.test_build.bucket)119 self.assertEqual(build.parameters, self.test_build.parameters)120 self.assertEqual(build.retry_of, self.test_build.key.id())121 def test_retry_not_found(self):122 with self.assertRaises(errors.BuildNotFoundError):123 service.retry(2)124 #################################### GET #####################################125 def test_get(self):126 self.test_build.put()127 build = service.get(self.test_build.key.id())128 self.assertEqual(build, self.test_build)129 def test_get_nonexistent_build(self):130 self.assertIsNone(service.get(42))131 def test_get_with_auth_error(self):132 self.mock_cannot(acl.Action.VIEW_BUILD)133 self.test_build.put()134 with self.assertRaises(auth.AuthorizationError):135 service.get(self.test_build.key.id())136 ################################### CANCEL ###################################137 def test_cancel(self):138 self.test_build.put()139 build = service.cancel(self.test_build.key.id())140 self.assertEqual(build.status, model.BuildStatus.COMPLETED)141 self.assertEqual(build.status_changed_time, utils.utcnow())142 self.assertEqual(build.complete_time, utils.utcnow())143 self.assertEqual(build.result, model.BuildResult.CANCELED)144 self.assertEqual(145 build.cancelation_reason, model.CancelationReason.CANCELED_EXPLICITLY)146 def test_cancel_is_idempotent(self):147 self.test_build.put()148 service.cancel(self.test_build.key.id())149 service.cancel(self.test_build.key.id())150 def test_cancel_started_build(self):151 self.lease()152 self.start()153 service.cancel(self.test_build.key.id())154 def test_cancel_nonexistent_build(self):155 with self.assertRaises(errors.BuildNotFoundError):156 service.cancel(1)157 def test_cancel_with_auth_error(self):158 self.test_build.put()159 self.mock_cannot(acl.Action.CANCEL_BUILD)160 with self.assertRaises(auth.AuthorizationError):161 service.cancel(self.test_build.key.id())162 def test_cancel_completed_build(self):163 self.test_build.status = model.BuildStatus.COMPLETED164 self.test_build.result = model.BuildResult.SUCCESS165 self.test_build.put()166 with self.assertRaises(errors.BuildIsCompletedError):167 service.cancel(self.test_build.key.id())168 #################################### SEARCH ##################################169 def test_search(self):170 build2 = model.Build(bucket=self.test_build.bucket)171 build2.put()172 self.test_build.tags = ['important:true']173 self.test_build.put()174 builds, _ = service.search(175 buckets=[self.test_build.bucket],176 tags=self.test_build.tags,177 )178 self.assertEqual(builds, [self.test_build])179 def test_search_without_buckets(self):180 get_available_buckets = mock.Mock()181 self.mock(acl, 'get_available_buckets', get_available_buckets)182 self.test_build.put()183 build2 = model.Build(bucket='other bucket')184 build2.put()185 get_available_buckets.return_value = [self.test_build.bucket]186 builds, _ = service.search()187 self.assertEqual(builds, [self.test_build])188 # All buckets are available.189 get_available_buckets.return_value = None190 builds, _ = service.search()191 self.assertEqual(builds, [self.test_build, build2])192 # No buckets are available.193 get_available_buckets.return_value = []194 builds, _ = service.search()195 self.assertEqual(builds, [])196 def test_search_many_tags(self):197 self.test_build.tags = ['important:true', 'author:ivan']198 self.test_build.put()199 build2 = model.Build(200 bucket=self.test_build.bucket,201 tags=self.test_build.tags[:1], # only one of two tags.202 )203 build2.put()204 # Search by both tags.205 builds, _ = service.search(206 tags=self.test_build.tags,207 buckets=[self.test_build.bucket],208 )209 self.assertEqual(builds, [self.test_build])210 def test_search_by_buildset(self):211 self.test_build.tags = ['buildset:x']212 self.test_build.put()213 build2 = model.Build(214 bucket='secret.bucket',215 tags=self.test_build.tags, # only one of two tags.216 )217 build2.put()218 get_available_buckets = mock.Mock(return_value=[self.test_build.bucket])219 self.mock(acl, 'get_available_buckets', get_available_buckets)220 builds, _ = service.search(tags=['buildset:x'])221 self.assertEqual(builds, [self.test_build])222 def test_search_bucket(self):223 self.test_build.put()224 build2 = model.Build(225 bucket='other bucket',226 )227 build2.put()228 builds, _ = service.search(buckets=[self.test_build.bucket])229 self.assertEqual(builds, [self.test_build])230 def test_search_by_status(self):231 self.test_build.put()232 build2 = model.Build(233 bucket=self.test_build.bucket,234 status=model.BuildStatus.COMPLETED,235 result=model.BuildResult.SUCCESS,236 )237 build2.put()238 builds, _ = service.search(239 buckets=[self.test_build.bucket],240 status=model.BuildStatus.SCHEDULED)241 self.assertEqual(builds, [self.test_build])242 builds, _ = service.search(243 buckets=[self.test_build.bucket],244 status=model.BuildStatus.COMPLETED,245 result=model.BuildResult.FAILURE)246 self.assertEqual(builds, [])247 def test_search_by_created_by(self):248 self.test_build.put()249 build2 = model.Build(250 bucket=self.test_build.bucket,251 created_by=auth.Identity.from_bytes('user:x@chromium.org')252 )253 build2.put()254 builds, _ = service.search(255 created_by='x@chromium.org', buckets=[self.test_build.bucket])256 self.assertEqual(builds, [build2])257 def test_search_by_retry_of(self):258 self.test_build.put()259 build2 = model.Build(260 bucket=self.test_build.bucket,261 retry_of=42,262 )263 build2.put()264 builds, _ = service.search(retry_of=42)265 self.assertEqual(builds, [build2])266 def test_search_by_created_by_with_bad_string(self):267 with self.assertRaises(errors.InvalidInputError):268 service.search(created_by='blah')269 def test_search_with_paging(self):270 self.put_many_builds()271 first_page, next_cursor = service.search(272 buckets=[self.test_build.bucket],273 max_builds=10,274 )275 self.assertEqual(len(first_page), 10)276 self.assertTrue(next_cursor)277 second_page, _ = service.search(278 buckets=[self.test_build.bucket],279 max_builds=10,280 start_cursor=next_cursor)281 self.assertEqual(len(second_page), 10)282 # no cover due to a bug in coverage (http://stackoverflow.com/a/35325514)283 self.assertTrue(284 any(new not in first_page for new in second_page)) # pragma: no cover285 def test_search_with_bad_tags(self):286 def test_bad_tag(tags):287 with self.assertRaises(errors.InvalidInputError):288 service.search(buckets=['bucket'], tags=tags)289 test_bad_tag(['x'])290 test_bad_tag([1])291 test_bad_tag({})292 test_bad_tag(1)293 def test_search_with_bad_buckets(self):294 with self.assertRaises(errors.InvalidInputError):295 service.search(buckets={})296 with self.assertRaises(errors.InvalidInputError):297 service.search(buckets=[1])298 def test_search_with_non_number_max_builds(self):299 with self.assertRaises(errors.InvalidInputError):300 service.search(buckets=['b'], tags=['a:b'], max_builds='a')301 def test_search_with_negative_max_builds(self):302 with self.assertRaises(errors.InvalidInputError):303 service.search(buckets=['b'], tags=['a:b'], max_builds=-2)304 #################################### PEEK ####################################305 def test_peek(self):306 self.test_build.put()307 builds, _ = service.peek(buckets=[self.test_build.bucket])308 self.assertEqual(builds, [self.test_build])309 def test_peek_multi(self):310 self.test_build.key = ndb.Key(model.Build, model.new_build_id())311 self.test_build.put()312 # We test that peek returns builds in decreasing order of the build key. The313 # build key is derived from the inverted current time, so later builds get314 # smaller ids. Only exception: if the time is the same, randomness decides315 # the order. So artificially create an id here to avoid flakiness.316 build2 = model.Build(id=self.test_build.key.id() - 1, bucket='bucket2')317 build2.put()318 builds, _ = service.peek(buckets=[self.test_build.bucket, 'bucket2'])319 self.assertEqual(builds, [self.test_build, build2])320 def test_peek_with_paging(self):321 self.put_many_builds()322 first_page, next_cursor = service.peek(323 buckets=[self.test_build.bucket])324 self.assertTrue(first_page)325 self.assertTrue(next_cursor)326 second_page, _ = service.peek(327 buckets=[self.test_build.bucket], start_cursor=next_cursor)328 self.assertTrue(all(b not in second_page for b in first_page))329 def test_peek_with_bad_cursor(self):330 self.put_many_builds()331 with self.assertRaises(errors.InvalidInputError):332 service.peek(buckets=[self.test_build.bucket], start_cursor='abc')333 def test_peek_without_buckets(self):334 with self.assertRaises(errors.InvalidInputError):335 service.peek(buckets=[])336 def test_peek_with_auth_error(self):337 self.mock_cannot(acl.Action.SEARCH_BUILDS)338 self.test_build.put()339 with self.assertRaises(auth.AuthorizationError):340 service.peek(buckets=[self.test_build.bucket])341 def test_peek_does_not_return_leased_builds(self):342 self.test_build.put()343 self.lease()344 builds, _ = service.peek([self.test_build.bucket])345 self.assertFalse(builds)346 def test_peek_200_builds(self):347 for _ in xrange(200):348 model.Build(bucket=self.test_build.bucket).put()349 builds, _ = service.peek([self.test_build.bucket], max_builds=200)350 self.assertTrue(len(builds) <= 100)351 #################################### LEASE ###################################352 def lease(self, lease_expiration_date=None):353 if self.test_build.key is None:354 self.test_build.put()355 success, self.test_build = service.lease(356 self.test_build.key.id(),357 lease_expiration_date=lease_expiration_date,358 )359 return success360 def test_lease(self):361 expiration_date = utils.utcnow() + datetime.timedelta(minutes=1)362 self.assertTrue(self.lease(lease_expiration_date=expiration_date))363 self.assertTrue(self.test_build.is_leased)364 self.assertGreater(self.test_build.lease_expiration_date, utils.utcnow())365 self.assertEqual(self.test_build.leasee, self.current_identity)366 def test_lease_build_with_auth_error(self):367 self.mock_cannot(acl.Action.LEASE_BUILD)368 build = self.test_build369 build.put()370 with self.assertRaises(auth.AuthorizationError):371 self.lease()372 def test_cannot_lease_a_leased_build(self):373 build = self.test_build374 build.put()375 self.assertTrue(self.lease())376 self.assertFalse(self.lease())377 def test_cannot_lease_a_nonexistent_build(self):378 with self.assertRaises(errors.BuildNotFoundError):379 service.lease(build_id=42)380 def test_cannot_lease_for_whole_day(self):381 with self.assertRaises(errors.InvalidInputError):382 self.lease(383 lease_expiration_date=utils.utcnow() + datetime.timedelta(days=1))384 def test_cannot_set_expiration_date_to_past(self):385 with self.assertRaises(errors.InvalidInputError):386 yesterday = utils.utcnow() - datetime.timedelta(days=1)387 self.lease(lease_expiration_date=yesterday)388 def test_cannot_lease_with_non_datetime_expiration_date(self):389 with self.assertRaises(errors.InvalidInputError):390 self.lease(lease_expiration_date=1)391 def test_leasing_regenerates_lease_key(self):392 orig_lease_key = 42393 self.lease()394 self.assertNotEqual(self.test_build.lease_key, orig_lease_key)395 def test_cannot_lease_completed_build(self):396 build = self.test_build397 build.status = model.BuildStatus.COMPLETED398 build.result = model.BuildResult.SUCCESS399 build.put()400 self.assertFalse(self.lease())401 ################################### UNELASE ##################################402 def test_reset(self):403 self.lease()404 build = service.reset(self.test_build.key.id())405 self.assertEqual(build.status, model.BuildStatus.SCHEDULED)406 self.assertEqual(build.status_changed_time, utils.utcnow())407 self.assertIsNone(build.lease_key)408 self.assertIsNone(build.lease_expiration_date)409 self.assertIsNone(build.leasee)410 self.assertTrue(self.lease())411 def test_reset_is_idempotent(self):412 self.lease()413 build_id = self.test_build.key.id()414 service.reset(build_id)415 service.reset(build_id)416 def test_reset_completed_build(self):417 self.test_build.status = model.BuildStatus.COMPLETED418 self.test_build.result = model.BuildResult.SUCCESS419 self.test_build.put()420 with self.assertRaises(errors.BuildIsCompletedError):421 service.reset(self.test_build.key.id())422 def test_cannot_reset_nonexistent_build(self):423 with self.assertRaises(errors.BuildNotFoundError):424 service.reset(123)425 def test_reset_with_auth_error(self):426 self.lease()427 self.mock_cannot(acl.Action.RESET_BUILD)428 with self.assertRaises(auth.AuthorizationError):429 service.reset(self.test_build.key.id())430 #################################### START ###################################431 def test_validate_malformed_url(self):432 with self.assertRaises(errors.InvalidInputError):433 service.validate_url('svn://sdfsf')434 def test_validate_relative_url(self):435 with self.assertRaises(errors.InvalidInputError):436 service.validate_url('sdfsf')437 def test_validate_nonstring_url(self):438 with self.assertRaises(errors.InvalidInputError):439 service.validate_url(123)440 def start(self, url=None, lease_key=None):441 self.test_build = service.start(442 self.test_build.key.id(),443 lease_key or self.test_build.lease_key,444 url=url)445 def test_start(self):446 self.lease()447 self.start(url='http://localhost')448 self.assertEqual(self.test_build.status, model.BuildStatus.STARTED)449 self.assertEqual(self.test_build.url, 'http://localhost')450 def test_start_started_build(self):451 self.lease()452 build_id = self.test_build.key.id()453 lease_key = self.test_build.lease_key454 url = 'http://localhost/'455 service.start(build_id, lease_key, url)456 service.start(build_id, lease_key, url)457 service.start(build_id, lease_key, url + '1')458 def test_start_non_leased_build(self):459 self.test_build.put()460 with self.assertRaises(errors.LeaseExpiredError):461 service.start(self.test_build.key.id(), 42)462 def test_start_completed_build(self):463 self.test_build.status = model.BuildStatus.COMPLETED464 self.test_build.result = model.BuildResult.SUCCESS465 self.test_build.put()466 with self.assertRaises(errors.BuildIsCompletedError):467 service.start(self.test_build.key.id(), 42)468 def test_start_without_lease_key(self):469 with self.assertRaises(errors.InvalidInputError):470 service.start(1, None)471 @contextlib.contextmanager472 def callback_test(self):473 self.mock(notifications, 'enqueue_callback_task_if_needed', mock.Mock())474 self.test_build.pubsub_callback = model.PubSubCallback(475 topic='projects/example/topic/buildbucket',476 user_data='hello',477 auth_token='secret',478 )479 self.test_build.put()480 yield481 self.assertTrue(notifications.enqueue_callback_task_if_needed.called)482 def test_start_creates_notification_task(self):483 self.lease()484 with self.callback_test():485 self.start()486 ################################## HEARTBEAT #################################487 def test_heartbeat(self):488 self.lease()489 new_expiration_date = utils.utcnow() + datetime.timedelta(minutes=1)490 build = service.heartbeat(491 self.test_build.key.id(), self.test_build.lease_key,492 lease_expiration_date=new_expiration_date)493 self.assertEqual(build.lease_expiration_date, new_expiration_date)494 def test_heartbeat_completed(self):495 self.test_build.status = model.BuildStatus.COMPLETED496 self.test_build.result = model.BuildResult.CANCELED497 self.test_build.cancelation_reason = (498 model.CancelationReason.CANCELED_EXPLICITLY)499 self.test_build.put()500 new_expiration_date = utils.utcnow() + datetime.timedelta(minutes=1)501 with self.assertRaises(errors.BuildIsCompletedError):502 service.heartbeat(503 self.test_build.key.id(), 0,504 lease_expiration_date=new_expiration_date)505 def test_heartbeat_batch(self):506 self.lease()507 new_expiration_date = utils.utcnow() + datetime.timedelta(minutes=1)508 results = service.heartbeat_batch(509 [510 {511 'build_id': self.test_build.key.id(),512 'lease_key': self.test_build.lease_key,513 'lease_expiration_date': new_expiration_date514 },515 {516 'build_id': 42,517 'lease_key': 42,518 'lease_expiration_date': new_expiration_date,519 },520 ])521 self.assertEqual(len(results), 2)522 self.test_build = self.test_build.key.get()523 self.assertEqual(524 results[0],525 (self.test_build.key.id(), self.test_build, None))526 self.assertIsNone(results[1][1])527 self.assertTrue(isinstance(results[1][2], errors.BuildNotFoundError))528 def test_heartbeat_without_expiration_date(self):529 self.lease()530 with self.assertRaises(errors.InvalidInputError):531 service.heartbeat(532 self.test_build.key.id(), self.test_build.lease_key,533 lease_expiration_date=None)534 ################################### COMPLETE #################################535 def succeed(self, **kwargs):536 self.test_build = service.succeed(537 self.test_build.key.id(), self.test_build.lease_key, **kwargs)538 def test_succeed(self):539 self.lease()540 self.start()541 self.succeed()542 self.assertEqual(self.test_build.status, model.BuildStatus.COMPLETED)543 self.assertEqual(self.test_build.status_changed_time, utils.utcnow())544 self.assertEqual(self.test_build.result, model.BuildResult.SUCCESS)545 self.assertIsNotNone(self.test_build.complete_time)546 def test_succeed_timed_out_build(self):547 self.test_build.status = model.BuildStatus.COMPLETED548 self.test_build.result = model.BuildResult.CANCELED549 self.test_build.cancelation_reason = model.CancelationReason.TIMEOUT550 self.test_build.put()551 with self.assertRaises(errors.BuildIsCompletedError):552 service.succeed(self.test_build.key.id(), 42)553 def test_succeed_is_idempotent(self):554 self.lease()555 self.start()556 build_id = self.test_build.key.id()557 lease_key = self.test_build.lease_key558 service.succeed(build_id, lease_key)559 service.succeed(build_id, lease_key)560 def test_succeed_with_new_tags(self):561 self.test_build.tags = ['a:1']562 self.test_build.put()563 self.lease()564 self.start()565 self.succeed(new_tags=['b:2'])566 self.assertEqual(self.test_build.tags, ['a:1', 'b:2'])567 def test_fail(self):568 self.lease()569 self.start()570 self.test_build = service.fail(571 self.test_build.key.id(), self.test_build.lease_key)572 self.assertEqual(self.test_build.status, model.BuildStatus.COMPLETED)573 self.assertEqual(self.test_build.status_changed_time, utils.utcnow())574 self.assertEqual(self.test_build.result, model.BuildResult.FAILURE)575 self.assertIsNotNone(self.test_build.complete_time)576 def test_fail_with_details(self):577 self.lease()578 self.start()579 result_details = {'transient_failure': True}580 self.test_build = service.fail(581 self.test_build.key.id(), self.test_build.lease_key,582 result_details=result_details)583 self.assertEqual(self.test_build.result_details, result_details)584 def test_complete_with_url(self):585 self.lease()586 self.start()587 url = 'http://localhost/1'588 self.succeed(url=url)589 self.assertEqual(self.test_build.url, url)590 def test_complete_not_started_build(self):591 self.lease()592 self.succeed()593 def test_completion_creates_notification_task(self):594 self.lease()595 self.start()596 with self.callback_test():597 self.succeed()598 ########################## RESET EXPIRED BUILDS ##############################599 def test_reschedule_expired_builds(self):600 self.test_build.lease_expiration_date = utils.utcnow()601 self.test_build.lease_key = 1602 self.test_build.leasee = self.current_identity603 self.test_build.put()604 service.reset_expired_builds()605 build = self.test_build.key.get()606 self.assertEqual(build.status, model.BuildStatus.SCHEDULED)607 self.assertIsNone(build.lease_key)608 def test_completed_builds_are_not_reset(self):609 self.test_build.status = model.BuildStatus.COMPLETED610 self.test_build.result = model.BuildResult.SUCCESS611 self.test_build.put()612 service.reset_expired_builds()613 build = self.test_build.key.get()614 self.assertEqual(build.status, model.BuildStatus.COMPLETED)615 def test_build_timeout(self):616 self.test_build.create_time = utils.utcnow() - datetime.timedelta(days=365)617 self.test_build.put()618 service.reset_expired_builds()619 build = self.test_build.key.get()620 self.assertEqual(build.status, model.BuildStatus.COMPLETED)621 self.assertEqual(build.result, model.BuildResult.CANCELED)622 self.assertEqual(build.cancelation_reason, model.CancelationReason.TIMEOUT)623 self.assertIsNone(build.lease_key)624 ########################## RESET EXPIRED BUILDS ##############################625 def test_delete_many_scheduled_builds(self):626 self.test_build.put()627 completed_build = model.Build(628 bucket=self.test_build.bucket,629 status=model.BuildStatus.COMPLETED,630 result=model.BuildResult.SUCCESS,631 )632 completed_build.put()633 self.assertIsNotNone(self.test_build.key.get())634 self.assertIsNotNone(completed_build.key.get())635 service._task_delete_many_builds(636 self.test_build.bucket, model.BuildStatus.SCHEDULED)637 self.assertIsNone(self.test_build.key.get())638 self.assertIsNotNone(completed_build.key.get())639 def test_delete_many_started_builds(self):640 self.test_build.put()641 started_build = model.Build(642 bucket=self.test_build.bucket,643 status=model.BuildStatus.STARTED,644 )645 started_build.put()646 completed_build = model.Build(647 bucket=self.test_build.bucket,648 status=model.BuildStatus.COMPLETED,649 result=model.BuildResult.SUCCESS,650 )651 completed_build.put()652 service._task_delete_many_builds(653 self.test_build.bucket, model.BuildStatus.STARTED)654 self.assertIsNotNone(self.test_build.key.get())655 self.assertIsNone(started_build.key.get())656 self.assertIsNotNone(completed_build.key.get())657 def test_delete_many_builds_with_tags(self):658 self.test_build.tags = ['tag:1']659 self.test_build.put()660 service._task_delete_many_builds(661 self.test_build.bucket, model.BuildStatus.SCHEDULED, tags=['tag:0'])662 self.assertIsNotNone(self.test_build.key.get())663 service._task_delete_many_builds(664 self.test_build.bucket, model.BuildStatus.SCHEDULED, tags=['tag:1'])665 self.assertIsNone(self.test_build.key.get())666 def test_delete_many_builds_created_by(self):667 self.test_build.created_by = auth.Identity('user', 'nodir@google.com')668 self.test_build.put()669 other_build = model.Build(bucket=self.test_build.bucket)670 other_build.put()671 service._task_delete_many_builds(672 self.test_build.bucket, model.BuildStatus.SCHEDULED,673 created_by='nodir@google.com')674 self.assertIsNone(self.test_build.key.get())675 self.assertIsNotNone(other_build.key.get())676 def test_delete_many_builds_auth_error(self):677 self.mock_cannot(acl.Action.DELETE_SCHEDULED_BUILDS)678 with self.assertRaises(auth.AuthorizationError):679 service.delete_many_builds(680 self.test_build.bucket, model.BuildStatus.SCHEDULED)681 def test_delete_many_builds_schedule_task(self):682 service.delete_many_builds(683 self.test_build.bucket, model.BuildStatus.SCHEDULED)684 def test_delete_many_completed_builds(self):685 with self.assertRaises(errors.InvalidInputError):686 service.delete_many_builds(687 self.test_build.bucket, model.BuildStatus.COMPLETED)688 ########################### LONGEST_PENDING_TIME ############################689 def test_longest_pending_time(self):690 builds = [691 model.Build(692 bucket='chromium',693 tags=['builder:x'],694 create_time=self.now - datetime.timedelta(minutes=10),695 ),696 model.Build(697 bucket='chromium',698 tags=['builder:x'],699 create_time=self.now - datetime.timedelta(minutes=20),700 ),701 model.Build(702 bucket='chromium',703 tags=['builder:y'],704 create_time=self.now - datetime.timedelta(minutes=30),705 ),706 ]707 for b in builds:708 b.put()709 actual = service.longest_pending_time('chromium', 'x')710 self.assertEqual(actual, datetime.timedelta(minutes=20))711 def test_longest_pending_time_invalid_input(self):712 with self.assertRaises(errors.InvalidInputError):713 service.longest_pending_time('', 'x')714 with self.assertRaises(errors.InvalidInputError):715 service.longest_pending_time('chromium', '')716 def test_longest_pending_time_no_builds(self):717 actual = service.longest_pending_time('chromium', 'x')718 self.assertEqual(actual, datetime.timedelta(0))719 def test_longest_pending_time_without_permissions(self):720 self.mock_cannot(acl.Action.ACCESS_BUCKET)721 with self.assertRaises(auth.AuthorizationError):...
api_test.py
Source:api_test.py
1# Copyright 2014 The Chromium Authors. All rights reserved.2# Use of this source code is governed by a BSD-style license that can be3# found in the LICENSE file.4import datetime5import json6import os7import sys8REPO_ROOT_DIR = os.path.abspath(os.path.join(9 os.path.realpath(__file__), '..', '..', '..', '..'))10sys.path.insert(0, os.path.join(11 REPO_ROOT_DIR, 'luci', 'appengine', 'third_party_local'))12from components import auth13from components import utils14from components.test_support import test_case15from google.appengine.ext import ndb16from testing_utils import testing17import mock18import gae_ts_mon19from test import config_test20import api21import config22import errors23import model24import service25class ApiTests(object):26 test_build = None27 future_ts = None28 future_date = None29 def setUpTests(self):30 gae_ts_mon.reset_for_unittest(disable=True)31 for a in dir(service):32 self.mock(service, a, mock.Mock())33 self.future_date = utils.utcnow() + datetime.timedelta(minutes=1)34 # future_ts is str because INT64 values are formatted as strings.35 self.future_ts = str(utils.datetime_to_timestamp(self.future_date))36 self.test_build = model.Build(37 id=1,38 bucket='chromium',39 parameters={40 'buildername': 'linux_rel',41 },42 )43 def expect_error(self, method_name, req, error_reason):44 res = self.call_api(method_name, req).json_body45 self.assertIsNotNone(res.get('error'))46 self.assertEqual(res['error']['reason'], error_reason)47 def test_expired_build_to_message(self):48 yesterday = utils.utcnow() - datetime.timedelta(days=1)49 yesterday_timestamp = utils.datetime_to_timestamp(yesterday)50 self.test_build.lease_key = 151 self.test_build.lease_expiration_date = yesterday52 msg = api.build_to_message(self.test_build)53 self.assertEqual(msg.lease_expiration_ts, yesterday_timestamp)54 ##################################### GET ####################################55 def test_get(self):56 self.test_build.lease_expiration_date = self.future_date57 build_id = self.test_build.key.id()58 service.get.return_value = self.test_build59 resp = self.call_api('get', {'id': build_id}).json_body60 service.get.assert_called_once_with(build_id)61 self.assertEqual(resp['build']['id'], str(build_id))62 self.assertEqual(resp['build']['bucket'], self.test_build.bucket)63 self.assertEqual(resp['build']['lease_expiration_ts'], self.future_ts)64 self.assertEqual(resp['build']['status'], 'SCHEDULED')65 self.assertEqual(66 resp['build']['parameters_json'], '{"buildername": "linux_rel"}')67 def test_get_nonexistent_build(self):68 service.get.return_value = None69 self.expect_error('get', {'id': 1}, 'BUILD_NOT_FOUND')70 ##################################### PUT ####################################71 def test_put(self):72 self.test_build.tags = ['owner:ivan']73 service.add.return_value = self.test_build74 req = {75 'client_operation_id': '42',76 'bucket': self.test_build.bucket,77 'tags': self.test_build.tags,78 'pubsub_callback': {79 'topic': 'projects/foo/topic/bar',80 'user_data': 'hello',81 'auth_token': 'secret',82 }83 }84 resp = self.call_api('put', req).json_body85 service.add.assert_called_once_with(86 bucket=self.test_build.bucket,87 tags=req['tags'],88 parameters=None,89 lease_expiration_date=None,90 client_operation_id='42',91 pubsub_callback=model.PubSubCallback(92 topic='projects/foo/topic/bar',93 user_data='hello',94 auth_token='secret',95 ),96 )97 self.assertEqual(resp['build']['id'], str(self.test_build.key.id()))98 self.assertEqual(resp['build']['bucket'], req['bucket'])99 self.assertEqual(resp['build']['tags'], req['tags'])100 def test_put_with_parameters(self):101 service.add.return_value = self.test_build102 req = {103 'bucket': self.test_build.bucket,104 'parameters_json': json.dumps(self.test_build.parameters),105 }106 resp = self.call_api('put', req).json_body107 self.assertEqual(resp['build']['parameters_json'], req['parameters_json'])108 def test_put_with_leasing(self):109 self.test_build.lease_expiration_date = self.future_date110 service.add.return_value = self.test_build111 req = {112 'bucket': self.test_build.bucket,113 'lease_expiration_ts': self.future_ts,114 }115 resp = self.call_api('put', req).json_body116 service.add.assert_called_once_with(117 bucket=self.test_build.bucket,118 tags=[],119 parameters=None,120 lease_expiration_date=self.future_date,121 client_operation_id=None,122 pubsub_callback=None,123 )124 self.assertEqual(125 resp['build']['lease_expiration_ts'], req['lease_expiration_ts'])126 def test_put_with_malformed_parameters_json(self):127 req = {128 'bucket': 'chromium',129 'parameters_json': '}non-json',130 }131 self.expect_error('put', req, 'INVALID_INPUT')132 #################################### RETRY ###################################133 def test_retry(self):134 build = model.Build(135 bucket='chromium',136 parameters={'builder_name': 'debug'},137 tags = ['a:b'],138 retry_of=2,139 )140 build.put()141 service.retry.return_value = build142 req = {143 'id': build.key.id(),144 'client_operation_id': '42',145 'pubsub_callback': {146 'topic': 'projects/foo/topic/bar',147 'user_data': 'hello',148 'auth_token': 'secret',149 }150 }151 resp = self.call_api('retry', req).json_body152 service.retry.assert_called_once_with(153 build.key.id(),154 client_operation_id='42',155 lease_expiration_date=None,156 pubsub_callback=model.PubSubCallback(157 topic='projects/foo/topic/bar',158 user_data='hello',159 auth_token='secret',160 ),161 )162 self.assertEqual(resp['build']['id'], str(build.key.id()))163 self.assertEqual(resp['build']['bucket'], build.bucket)164 self.assertEqual(165 json.loads(resp['build']['parameters_json']), build.parameters)166 self.assertEqual(resp['build']['retry_of'], '2')167 def test_retry_not_found(self):168 service.retry.side_effect = errors.BuildNotFoundError169 self.expect_error('retry', {'id': 42}, 'BUILD_NOT_FOUND')170 ################################## PUT_BATCH #################################171 def test_put_batch(self):172 self.test_build.tags = ['owner:ivan']173 build1_future = ndb.Future()174 build1_future.set_result(self.test_build)175 build2 = model.Build(id=2, bucket='v8')176 build2_future = ndb.Future()177 build2_future.set_result(build2)178 bad_build_future = ndb.Future()179 bad_build_future.set_exception(errors.InvalidInputError('Just bad'))180 service.add_async.side_effect = [181 build1_future, build2_future, bad_build_future]182 req = {183 'builds': [184 {185 'bucket': self.test_build.bucket,186 'tags': self.test_build.tags,187 'client_operation_id': '0',188 },189 {190 'bucket': build2.bucket,191 'client_operation_id': '1',192 },193 {194 'bucket': 'bad name',195 'client_operation_id': '2',196 },197 ],198 }199 resp = self.call_api('put_batch', req).json_body200 service.add_async.assert_any_call(201 bucket=self.test_build.bucket,202 tags=self.test_build.tags,203 parameters=None,204 lease_expiration_date=None,205 client_operation_id='0',206 pubsub_callback=None,207 )208 service.add_async.assert_any_call(209 bucket=build2.bucket,210 tags=[],211 parameters=None,212 lease_expiration_date=None,213 client_operation_id='1',214 pubsub_callback=None,215 )216 res0 = resp['results'][0]217 self.assertEqual(res0['client_operation_id'], '0')218 self.assertEqual(res0['build']['id'], str(self.test_build.key.id()))219 self.assertEqual(res0['build']['bucket'], self.test_build.bucket)220 self.assertEqual(res0['build']['tags'], self.test_build.tags)221 res1 = resp['results'][1]222 self.assertEqual(res1['client_operation_id'], '1')223 self.assertEqual(res1['build']['id'], str(build2.key.id()))224 self.assertEqual(res1['build']['bucket'], build2.bucket)225 res2 = resp['results'][2]226 self.assertEqual(res2, {227 'client_operation_id': '2',228 'error': {'reason': 'INVALID_INPUT', 'message': 'Just bad'},229 })230 #################################### SEARCH ##################################231 def test_search(self):232 self.test_build.put()233 service.search.return_value = ([self.test_build], 'the cursor')234 req = {235 'bucket': ['chromium'],236 'cancelation_reason': 'CANCELED_EXPLICITLY',237 'created_by': 'user:x@chromium.org',238 'result': 'CANCELED',239 'status': 'COMPLETED',240 'tag': ['important'],241 'retry_of': '42',242 }243 res = self.call_api('search', req).json_body244 service.search.assert_called_once_with(245 buckets=req['bucket'],246 tags=req['tag'],247 status=model.BuildStatus.COMPLETED,248 result=model.BuildResult.CANCELED,249 failure_reason=None,250 cancelation_reason=model.CancelationReason.CANCELED_EXPLICITLY,251 created_by='user:x@chromium.org',252 max_builds=None,253 start_cursor=None,254 retry_of=42,255 )256 self.assertEqual(len(res['builds']), 1)257 self.assertEqual(res['builds'][0]['id'], str(self.test_build.key.id()))258 self.assertEqual(res['next_cursor'], 'the cursor')259 ##################################### PEEK ###################################260 def test_peek(self):261 self.test_build.put()262 service.peek.return_value = ([self.test_build], 'the cursor')263 req = {'bucket': [self.test_build.bucket]}264 res = self.call_api('peek', req).json_body265 service.peek.assert_called_once_with(266 req['bucket'],267 max_builds=None,268 start_cursor=None,269 )270 self.assertEqual(len(res['builds']), 1)271 peeked_build = res['builds'][0]272 self.assertEqual(peeked_build['id'], str(self.test_build.key.id()))273 self.assertEqual(res['next_cursor'], 'the cursor')274 #################################### LEASE ###################################275 def test_lease(self):276 self.test_build.lease_expiration_date = self.future_date277 self.test_build.lease_key = 42278 service.lease.return_value = True, self.test_build279 req = {280 'id': self.test_build.key.id(),281 'lease_expiration_ts': self.future_ts,282 }283 res = self.call_api('lease', req).json_body284 service.lease.assert_called_once_with(285 self.test_build.key.id(),286 lease_expiration_date=self.future_date,287 )288 self.assertIsNone(res.get('error'))289 self.assertEqual(res['build']['id'], str(self.test_build.key.id()))290 self.assertEqual(res['build']['lease_key'], str(self.test_build.lease_key))291 self.assertEqual(292 res['build']['lease_expiration_ts'],293 req['lease_expiration_ts'])294 def test_lease_with_negative_expiration_date(self):295 req = {296 'id': self.test_build.key.id(),297 'lease_expiration_ts': 242894728472423847289472398,298 }299 self.expect_error('lease', req, 'INVALID_INPUT')300 def test_lease_unsuccessful(self):301 self.test_build.put()302 service.lease.return_value = (False, self.test_build)303 req = {304 'id': self.test_build.key.id(),305 'lease_expiration_ts': self.future_ts,306 }307 self.expect_error('lease', req, 'CANNOT_LEASE_BUILD')308 #################################### RESET ###################################309 def test_reset(self):310 service.reset.return_value = self.test_build311 req = {312 'id': self.test_build.key.id(),313 }314 res = self.call_api('reset', req).json_body315 service.reset.assert_called_once_with(self.test_build.key.id())316 self.assertIsNone(res.get('error'))317 self.assertEqual(res['build']['id'], str(self.test_build.key.id()))318 self.assertFalse('lease_key' in res['build'])319 #################################### START ###################################320 def test_start(self):321 self.test_build.url = 'http://localhost/build/1'322 service.start.return_value = self.test_build323 req = {324 'id': self.test_build.key.id(),325 'lease_key': 42,326 'url': self.test_build.url,327 }328 res = self.call_api('start', req).json_body329 service.start.assert_called_once_with(330 req['id'], req['lease_key'], url=req['url'])331 self.assertEqual(int(res['build']['id']), req['id'])332 self.assertEqual(res['build']['url'], req['url'])333 def test_start_completed_build(self):334 service.start.side_effect = errors.BuildIsCompletedError335 req = {336 'id': self.test_build.key.id(),337 'lease_key': 42,338 }339 res = self.call_api('start', req).json_body340 self.assertEqual(res['error']['reason'], 'BUILD_IS_COMPLETED')341 #################################### HEATBEAT ################################342 def test_heartbeat(self):343 self.test_build.lease_expiration_date = self.future_date344 service.heartbeat.return_value = self.test_build345 req = {346 'id': self.test_build.key.id(),347 'lease_key': 42,348 'lease_expiration_ts': self.future_ts,349 }350 res = self.call_api('heartbeat', req).json_body351 service.heartbeat.assert_called_once_with(352 req['id'], req['lease_key'], self.future_date)353 self.assertEqual(int(res['build']['id']), req['id'])354 self.assertEqual(355 res['build']['lease_expiration_ts'], req['lease_expiration_ts'],356 )357 def test_heartbeat_batch(self):358 self.test_build.lease_expiration_date = self.future_date359 build2 = model.Build(360 id=2,361 bucket='chromium',362 lease_expiration_date=self.future_date,363 )364 service.heartbeat_batch.return_value = [365 (self.test_build.key.id(), self.test_build, None),366 (build2.key.id(), None, errors.LeaseExpiredError())367 ]368 req = {369 'heartbeats': [{370 'build_id': self.test_build.key.id(),371 'lease_key': 42,372 'lease_expiration_ts': self.future_ts,373 }, {374 'build_id': build2.key.id(),375 'lease_key': 42,376 'lease_expiration_ts': self.future_ts,377 }],378 }379 res = self.call_api('heartbeat_batch', req).json_body380 service.heartbeat_batch.assert_called_any_with(381 self.test_build.key.id(), 42, self.future_date)382 service.heartbeat_batch.assert_called_any_with(383 build2.key.id(), 42, self.future_date)384 result1 = res['results'][0]385 self.assertEqual(int(result1['build_id']), self.test_build.key.id())386 self.assertEqual(result1['lease_expiration_ts'], self.future_ts)387 result2 = res['results'][1]388 self.assertEqual(int(result2['build_id']), build2.key.id())389 self.assertTrue(result2['error']['reason'] == 'LEASE_EXPIRED')390 def test_heartbeat_batch_with_internal_server_error(self):391 self.test_build.lease_expiration_date = self.future_date392 service.heartbeat_batch.return_value = [393 (self.test_build.key.id(), None, ValueError())394 ]395 req = {396 'heartbeats': [{397 'build_id': self.test_build.key.id(),398 'lease_key': 42,399 'lease_expiration_ts': self.future_ts,400 }],401 }402 self.call_api('heartbeat_batch', req, status=500)403 ################################## SUCCEED ###################################404 def test_succeed(self):405 service.succeed.return_value = self.test_build406 req = {407 'id': self.test_build.key.id(),408 'lease_key': 42,409 'new_tags': ['bot_id:bot42'],410 }411 res = self.call_api('succeed', req).json_body412 service.succeed.assert_called_once_with(413 req['id'], req['lease_key'], result_details=None, url=None,414 new_tags=['bot_id:bot42'])415 self.assertEqual(int(res['build']['id']), req['id'])416 def test_succeed_with_result_details(self):417 self.test_build.result_details = {'test_coverage': 100}418 self.test_build.tags = ['bot_id:bot42']419 service.succeed.return_value = self.test_build420 req = {421 'id': self.test_build.key.id(),422 'lease_key': 42,423 'result_details_json': json.dumps(self.test_build.result_details),424 }425 res = self.call_api('succeed', req).json_body426 _, kwargs = service.succeed.call_args427 self.assertEqual(428 kwargs['result_details'], self.test_build.result_details)429 self.assertEqual(430 res['build']['result_details_json'], req['result_details_json'])431 self.assertIn('bot_id:bot42', res['build']['tags'])432 #################################### FAIL ####################################433 def test_infra_failure(self):434 self.test_build.result_details = {'transient_error': True}435 self.test_build.failure_reason = model.FailureReason.INFRA_FAILURE436 self.test_build.tags = ['bot_id:bot42']437 service.fail.return_value = self.test_build438 req = {439 'id': self.test_build.key.id(),440 'lease_key': 42,441 'failure_reason': 'INFRA_FAILURE',442 'result_details_json': json.dumps(self.test_build.result_details),443 'new_tags': ['bot_id:bot42'],444 }445 res = self.call_api('fail', req).json_body446 service.fail.assert_called_once_with(447 req['id'], req['lease_key'],448 result_details=self.test_build.result_details,449 failure_reason=model.FailureReason.INFRA_FAILURE,450 url=None,451 new_tags=['bot_id:bot42'])452 self.assertEqual(int(res['build']['id']), req['id'])453 self.assertEqual(res['build']['failure_reason'], req['failure_reason'])454 self.assertEqual(455 res['build']['result_details_json'], req['result_details_json'])456 #################################### CANCEL ##################################457 def test_cancel(self):458 service.cancel.return_value = self.test_build459 req = {460 'id': self.test_build.key.id(),461 }462 res = self.call_api('cancel', req).json_body463 service.cancel.assert_called_once_with(req['id'])464 self.assertEqual(int(res['build']['id']), req['id'])465 ################################# CANCEL_BATCH ###############################466 def test_cancel_batch(self):467 service.cancel.side_effect = [468 self.test_build, errors.BuildIsCompletedError]469 req = {470 'build_ids': [self.test_build.key.id(), 2],471 }472 res = self.call_api('cancel_batch', req).json_body473 res0 = res['results'][0]474 self.assertEqual(int(res0['build_id']), self.test_build.key.id())475 self.assertEqual(int(res0['build']['id']), self.test_build.key.id())476 service.cancel.assert_any_call(self.test_build.key.id())477 res1 = res['results'][1]478 self.assertEqual(int(res1['build_id']), 2)479 self.assertEqual(res1['error']['reason'], 'BUILD_IS_COMPLETED')480 service.cancel.assert_any_call(2)481 ########################## DELETE_MANY_BUILDS ##############################482 def test_delete_many_builds(self):483 req = {484 'bucket': 'chromium',485 'status': 'SCHEDULED',486 'tags': ['tag:0'],487 'created_by': 'nodir@google.com',488 }489 self.call_api('delete_many_builds', req)490 ############################## GET_BUCKET ##################################491 @mock.patch('config.get_buildbucket_cfg_url', autospec=True)492 def test_get_bucket(self, get_buildbucket_cfg_url):493 get_buildbucket_cfg_url.return_value = 'https://example.com/buildbucket.cfg'494 bucket_cfg = """495 name: "master.tryserver.chromium.linux"496 acls {497 role: READER498 identity: "anonymous:anonymous"499 }500 """501 config.Bucket(502 id='master.tryserver.chromium.linux',503 project_id='chromium',504 revision='deadbeef',505 config_content=bucket_cfg,506 config_content_binary=config_test.text_to_binary(bucket_cfg),507 ).put()508 req = {509 'bucket': 'master.tryserver.chromium.linux',510 }511 res = self.call_api('get_bucket', req).json_body512 self.assertEqual(res, {513 'name': 'master.tryserver.chromium.linux',514 'project_id': 'chromium',515 'config_file_content': bucket_cfg,516 'config_file_url': 'https://example.com/buildbucket.cfg',517 'config_file_rev': 'deadbeef',518 })519 @mock.patch('components.auth.is_admin', autospec=True)520 def test_get_bucket_not_found(self, is_admin):521 is_admin.return_value = True522 req = {523 'bucket': 'non-existent',524 }525 self.call_api('get_bucket', req, status=404)526 def test_get_bucket_with_auth_error(self):527 req = {528 'bucket': 'secret-project',529 }530 self.call_api('get_bucket', req, status=403)531 #################################### ERRORS ##################################532 def error_test(self, error_class, reason):533 service.get.side_effect = error_class534 self.expect_error('get', {'id': 123}, reason)535 def test_build_not_found_error(self):536 self.error_test(errors.BuildNotFoundError, 'BUILD_NOT_FOUND')537 def test_invalid_input_error(self):538 self.error_test(errors.InvalidInputError, 'INVALID_INPUT')539 def test_lease_expired_error(self):540 self.error_test(errors.LeaseExpiredError, 'LEASE_EXPIRED')541 def test_auth_error(self):542 service.get.side_effect = auth.AuthorizationError543 self.call_api('get', {'id': 123}, status=403)544 ############################# LONGEST_PENDING_TIME ###########################545 def test_longest_pending_time(self):546 service.longest_pending_time.return_value = datetime.timedelta(seconds=42)547 req = {548 'bucket': 'chromium',549 'builder': 'x',550 }551 res = self.call_api('longest_pending_time', req).json_body552 self.assertEqual(res['longest_pending_time_sec'], 42)553 service.longest_pending_time.assert_called_once_with('chromium', 'x')554class EndpointsApiTest(testing.EndpointsTestCase, ApiTests):555 api_service_cls = api.BuildBucketApi556 def setUp(self):557 super(EndpointsApiTest, self).setUp()558 self.setUpTests()559class Webapp2ApiTest(test_case.Webapp2EndpointsTestCase, ApiTests):560 api_service_cls = api.BuildBucketApi561 def setUp(self):562 super(Webapp2ApiTest, self).setUp()...
test_gcp_cloud_build_hook.py
Source:test_gcp_cloud_build_hook.py
1# -*- coding: utf-8 -*-2#3# Licensed to the Apache Software Foundation (ASF) under one4# or more contributor license agreements. See the NOTICE file5# distributed with this work for additional information6# regarding copyright ownership. The ASF licenses this file7# to you under the Apache License, Version 2.0 (the8# "License"); you may not use this file except in compliance9# with the License. You may obtain a copy of the License at10#11# http://www.apache.org/licenses/LICENSE-2.012#13# Unless required by applicable law or agreed to in writing,14# software distributed under the License is distributed on an15# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY16# KIND, either express or implied. See the License for the17# specific language governing permissions and limitations18# under the License.19"""20Tests for Google Cloud Build Hook21"""22import unittest23import six24from airflow import AirflowException25from airflow.contrib.hooks.gcp_cloud_build_hook import CloudBuildHook26from tests.compat import mock27from tests.contrib.utils.base_gcp_mock import (28 mock_base_gcp_hook_default_project_id,29 mock_base_gcp_hook_no_default_project_id,30)31TEST_CREATE_BODY = {32 "source": {"storageSource": {"bucket": "cloud-build-examples", "object": "node-docker-example.tar.gz"}},33 "steps": [34 {"name": "gcr.io/cloud-builders/docker", "args": ["build", "-t", "gcr.io/$PROJECT_ID/my-image", "."]}35 ],36 "images": ["gcr.io/$PROJECT_ID/my-image"],37}38TEST_BUILD = {"name": "build-name", "metadata": {"build": {"id": "AAA"}}}39TEST_WAITING_OPERATION = {"done": False, "response": "response"}40TEST_DONE_OPERATION = {"done": True, "response": "response"}41TEST_ERROR_OPERATION = {"done": True, "response": "response", "error": "error"}42TEST_PROJECT_ID = "cloud-build-project-id"43class TestCloudBuildHookWithPassedProjectId(unittest.TestCase):44 hook = None45 def setUp(self):46 with mock.patch(47 "airflow.contrib.hooks.gcp_api_base_hook.GoogleCloudBaseHook.__init__",48 new=mock_base_gcp_hook_default_project_id,49 ):50 self.hook = CloudBuildHook(gcp_conn_id="test")51 @mock.patch("airflow.contrib.hooks.gcp_cloud_build_hook.CloudBuildHook.get_conn")52 def test_build_immediately_complete(self, get_conn_mock):53 service_mock = get_conn_mock.return_value54 service_mock.projects.return_value\55 .builds.return_value\56 .create.return_value\57 .execute.return_value = TEST_BUILD58 service_mock.projects.return_value.\59 builds.return_value.\60 get.return_value.\61 execute.return_value = TEST_BUILD62 service_mock.operations.return_value.\63 get.return_value.\64 execute.return_value = TEST_DONE_OPERATION65 result = self.hook.create_build(body={}, project_id=TEST_PROJECT_ID)66 service_mock.projects.return_value.builds.return_value.create.assert_called_once_with(67 body={}, projectId=TEST_PROJECT_ID68 )69 self.assertEqual(result, TEST_BUILD)70 @mock.patch("airflow.contrib.hooks.gcp_cloud_build_hook.CloudBuildHook.get_conn")71 @mock.patch("airflow.contrib.hooks.gcp_cloud_build_hook.time.sleep")72 def test_waiting_operation(self, _, get_conn_mock):73 service_mock = get_conn_mock.return_value74 service_mock.projects.return_value.builds.return_value.create.return_value.execute.return_value = (75 TEST_BUILD76 )77 service_mock.projects.return_value.builds.return_value.get.return_value.execute.return_value = (78 TEST_BUILD79 )80 execute_mock = mock.Mock(81 **{"side_effect": [TEST_WAITING_OPERATION, TEST_DONE_OPERATION, TEST_DONE_OPERATION]}82 )83 service_mock.operations.return_value.get.return_value.execute = execute_mock84 result = self.hook.create_build(body={}, project_id=TEST_PROJECT_ID)85 self.assertEqual(result, TEST_BUILD)86 @mock.patch("airflow.contrib.hooks.gcp_cloud_build_hook.CloudBuildHook.get_conn")87 @mock.patch("airflow.contrib.hooks.gcp_cloud_build_hook.time.sleep")88 def test_error_operation(self, _, get_conn_mock):89 service_mock = get_conn_mock.return_value90 service_mock.projects.return_value.builds.return_value.create.return_value.execute.return_value = (91 TEST_BUILD92 )93 execute_mock = mock.Mock(**{"side_effect": [TEST_WAITING_OPERATION, TEST_ERROR_OPERATION]})94 service_mock.operations.return_value.get.return_value.execute = execute_mock95 with six.assertRaisesRegex(self, AirflowException, "error"):96 self.hook.create_build(body={})97class TestGcpComputeHookWithDefaultProjectIdFromConnection(unittest.TestCase):98 hook = None99 def setUp(self):100 with mock.patch(101 "airflow.contrib.hooks.gcp_api_base_hook.GoogleCloudBaseHook.__init__",102 new=mock_base_gcp_hook_default_project_id,103 ):104 self.hook = CloudBuildHook(gcp_conn_id="test")105 @mock.patch("airflow.contrib.hooks.gcp_cloud_build_hook.CloudBuildHook.get_conn")106 def test_build_immediately_complete(self, get_conn_mock):107 service_mock = get_conn_mock.return_value108 service_mock.projects.return_value.builds.return_value.create.return_value.execute.return_value = (109 TEST_BUILD110 )111 service_mock.projects.return_value.builds.return_value.get.return_value.execute.return_value = (112 TEST_BUILD113 )114 service_mock.operations.return_value.get.return_value.execute.return_value = TEST_DONE_OPERATION115 result = self.hook.create_build(body={})116 service_mock.projects.return_value.builds.return_value.create.assert_called_once_with(117 body={}, projectId='example-project'118 )119 self.assertEqual(result, TEST_BUILD)120 @mock.patch("airflow.contrib.hooks.gcp_cloud_build_hook.CloudBuildHook.get_conn")121 @mock.patch("airflow.contrib.hooks.gcp_cloud_build_hook.time.sleep")122 def test_waiting_operation(self, _, get_conn_mock):123 service_mock = get_conn_mock.return_value124 service_mock.projects.return_value.builds.return_value.create.return_value.execute.return_value = (125 TEST_BUILD126 )127 service_mock.projects.return_value.builds.return_value.get.return_value.execute.return_value = (128 TEST_BUILD129 )130 execute_mock = mock.Mock(131 **{"side_effect": [TEST_WAITING_OPERATION, TEST_DONE_OPERATION, TEST_DONE_OPERATION]}132 )133 service_mock.operations.return_value.get.return_value.execute = execute_mock134 result = self.hook.create_build(body={})135 self.assertEqual(result, TEST_BUILD)136 @mock.patch("airflow.contrib.hooks.gcp_cloud_build_hook.CloudBuildHook.get_conn")137 @mock.patch("airflow.contrib.hooks.gcp_cloud_build_hook.time.sleep")138 def test_error_operation(self, _, get_conn_mock):139 service_mock = get_conn_mock.return_value140 service_mock.projects.return_value.builds.return_value.create.return_value.execute.return_value = (141 TEST_BUILD142 )143 execute_mock = mock.Mock(**{"side_effect": [TEST_WAITING_OPERATION, TEST_ERROR_OPERATION]})144 service_mock.operations.return_value.get.return_value.execute = execute_mock145 with six.assertRaisesRegex(self, AirflowException, "error"):146 self.hook.create_build(body={})147class TestCloudBuildHookWithoutProjectId(unittest.TestCase):148 hook = None149 def setUp(self):150 with mock.patch(151 "airflow.contrib.hooks.gcp_api_base_hook.GoogleCloudBaseHook.__init__",152 new=mock_base_gcp_hook_no_default_project_id,153 ):154 self.hook = CloudBuildHook(gcp_conn_id="test")155 @mock.patch("airflow.contrib.hooks.gcp_cloud_build_hook.CloudBuildHook.get_conn")156 def test_create_build(self, _):157 with self.assertRaises(AirflowException) as e:158 self.hook.create_build(body={})159 self.assertEqual(160 "The project id must be passed either as keyword project_id parameter or as project_id extra in "161 "GCP connection definition. Both are not set!",162 str(e.exception),...
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!