From a3b6fd06f9d70795dc18d4ce8c7d66f6bf5a9c04 Mon Sep 17 00:00:00 2001 From: Edward Lemur Date: Mon, 2 Mar 2020 22:16:15 +0000 Subject: [PATCH] Reland "my_activity.py: Run using vpython3 by default." Fix issue with gerrit_util.py and add tests. Change-Id: Ie523ea59ddb93cf5c7fa35f3761310ce96747720 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/2081092 Reviewed-by: Anthony Polito Commit-Queue: Edward Lesmes --- gerrit_util.py | 32 +++++++++----- my_activity.py | 4 +- tests/gerrit_util_test.py | 92 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 13 deletions(-) diff --git a/gerrit_util.py b/gerrit_util.py index 3a56fa9d6..457a6de96 100644 --- a/gerrit_util.py +++ b/gerrit_util.py @@ -60,6 +60,12 @@ def time_sleep(seconds): return time.sleep(seconds) +def time_time(): + # Use this so that it can be mocked in tests without interfering with python + # system machinery. + return time.time() + + class GerritError(Exception): """Exception class for errors commuicating with the gerrit-on-borg service.""" def __init__(self, http_status, message, *args, **kwargs): @@ -286,22 +292,24 @@ class GceAuthenticator(Authenticator): @classmethod def _test_is_gce(cls): # Based on https://cloud.google.com/compute/docs/metadata#runninggce - try: - resp, _ = cls._get(cls._INFO_URL) - except (socket.error, httplib2.HttpLib2Error): - # Could not resolve URL. + resp, _ = cls._get(cls._INFO_URL) + if resp is None: return False return resp.get('metadata-flavor') == 'Google' @staticmethod def _get(url, **kwargs): next_delay_sec = 1 - for i in xrange(TRY_LIMIT): + for i in range(TRY_LIMIT): p = urllib.parse.urlparse(url) if p.scheme not in ('http', 'https'): raise RuntimeError( "Don't know how to work with protocol '%s'" % protocol) - resp, contents = httplib2.Http().request(url, 'GET', **kwargs) + try: + resp, contents = httplib2.Http().request(url, 'GET', **kwargs) + except (socket.error, httplib2.HttpLib2Error) as e: + LOGGER.debug('GET [%s] raised %s', url, e) + return None, None LOGGER.debug('GET [%s] #%d/%d (%d)', url, i+1, TRY_LIMIT, resp.status) if resp.status < 500: return (resp, contents) @@ -313,19 +321,19 @@ class GceAuthenticator(Authenticator): next_delay_sec, TRY_LIMIT - i - 1) time_sleep(next_delay_sec) next_delay_sec *= 2 + return None, None @classmethod def _get_token_dict(cls): - if cls._token_cache: - # If it expires within 25 seconds, refresh. - if cls._token_expiration < time.time() - 25: - return cls._token_cache + # If cached token is valid for at least 25 seconds, return it. + if cls._token_cache and time_time() + 25 < cls._token_expiration: + return cls._token_cache resp, contents = cls._get(cls._ACQUIRE_URL, headers=cls._ACQUIRE_HEADERS) - if resp.status != 200: + if resp is None or resp.status != 200: return None cls._token_cache = json.loads(contents) - cls._token_expiration = cls._token_cache['expires_in'] + time.time() + cls._token_expiration = cls._token_cache['expires_in'] + time_time() return cls._token_cache def get_auth_header(self, _host): diff --git a/my_activity.py b/my_activity.py index b4be6fd20..0904b2c0a 100755 --- a/my_activity.py +++ b/my_activity.py @@ -1,4 +1,4 @@ -#!/usr/bin/env vpython +#!/usr/bin/env vpython3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -46,6 +46,8 @@ import gerrit_util if sys.version_info.major == 2: + logging.warning( + 'Python 2 is deprecated. Run my_activity.py using vpython3.') import urllib as urllib_parse else: import urllib.parse as urllib_parse diff --git a/tests/gerrit_util_test.py b/tests/gerrit_util_test.py index 77b3c0f07..bf11e5265 100644 --- a/tests/gerrit_util_test.py +++ b/tests/gerrit_util_test.py @@ -9,6 +9,7 @@ from __future__ import unicode_literals import base64 +import httplib2 import json import os import sys @@ -175,6 +176,97 @@ class CookiesAuthenticatorTest(unittest.TestCase): self.assertIsNone(auth.get_auth_email('some-review.example.com')) +class GceAuthenticatorTest(unittest.TestCase): + def setUp(self): + super(GceAuthenticatorTest, self).setUp() + mock.patch('httplib2.Http').start() + mock.patch('os.getenv', return_value=None).start() + mock.patch('gerrit_util.time_sleep').start() + mock.patch('gerrit_util.time_time').start() + self.addCleanup(mock.patch.stopall) + # GceAuthenticator has class variables that cache the results. Build a new + # class for every test to avoid inter-test dependencies. + class GceAuthenticator(gerrit_util.GceAuthenticator): + pass + self.GceAuthenticator = GceAuthenticator + + def testIsGce_EnvVarSkip(self, *_mocks): + os.getenv.return_value = '1' + self.assertFalse(self.GceAuthenticator.is_gce()) + os.getenv.assert_called_once_with('SKIP_GCE_AUTH_FOR_GIT') + + def testIsGce_Error(self): + httplib2.Http().request.side_effect = httplib2.HttpLib2Error + self.assertFalse(self.GceAuthenticator.is_gce()) + + def testIsGce_500(self): + httplib2.Http().request.return_value = (mock.Mock(status=500), None) + self.assertFalse(self.GceAuthenticator.is_gce()) + self.assertEqual( + [mock.call(1), mock.call(2)], gerrit_util.time_sleep.mock_calls) + + def testIsGce_FailsThenSucceeds(self): + response = mock.Mock(status=200) + response.get.return_value = 'Google' + httplib2.Http().request.side_effect = [ + (mock.Mock(status=500), None), + (response, 'who cares'), + ] + self.assertTrue(self.GceAuthenticator.is_gce()) + + def testIsGce_MetadataFlavorIsNotGoogle(self): + response = mock.Mock(status=200) + response.get.return_value = None + httplib2.Http().request.return_value = (response, 'who cares') + self.assertFalse(self.GceAuthenticator.is_gce()) + response.get.assert_called_once_with('metadata-flavor') + + def testIsGce_ResultIsCached(self): + response = mock.Mock(status=200) + response.get.return_value = 'Google' + httplib2.Http().request.side_effect = [(response, 'who cares')] + self.assertTrue(self.GceAuthenticator.is_gce()) + self.assertTrue(self.GceAuthenticator.is_gce()) + httplib2.Http().request.assert_called_once() + + def testGetAuthHeader_Error(self): + httplib2.Http().request.side_effect = httplib2.HttpLib2Error + self.assertIsNone(self.GceAuthenticator().get_auth_header('')) + + def testGetAuthHeader_500(self): + httplib2.Http().request.return_value = (mock.Mock(status=500), None) + self.assertIsNone(self.GceAuthenticator().get_auth_header('')) + + def testGetAuthHeader_Non200(self): + httplib2.Http().request.return_value = (mock.Mock(status=403), None) + self.assertIsNone(self.GceAuthenticator().get_auth_header('')) + + def testGetAuthHeader_OK(self): + httplib2.Http().request.return_value = ( + mock.Mock(status=200), + '{"expires_in": 125, "token_type": "TYPE", "access_token": "TOKEN"}') + gerrit_util.time_time.return_value = 0 + self.assertEqual('TYPE TOKEN', self.GceAuthenticator().get_auth_header('')) + + def testGetAuthHeader_Cache(self): + httplib2.Http().request.return_value = ( + mock.Mock(status=200), + '{"expires_in": 125, "token_type": "TYPE", "access_token": "TOKEN"}') + gerrit_util.time_time.return_value = 0 + self.assertEqual('TYPE TOKEN', self.GceAuthenticator().get_auth_header('')) + self.assertEqual('TYPE TOKEN', self.GceAuthenticator().get_auth_header('')) + httplib2.Http().request.assert_called_once() + + def testGetAuthHeader_CacheOld(self): + httplib2.Http().request.return_value = ( + mock.Mock(status=200), + '{"expires_in": 125, "token_type": "TYPE", "access_token": "TOKEN"}') + gerrit_util.time_time.side_effect = [0, 100, 200] + self.assertEqual('TYPE TOKEN', self.GceAuthenticator().get_auth_header('')) + self.assertEqual('TYPE TOKEN', self.GceAuthenticator().get_auth_header('')) + self.assertEqual(2, len(httplib2.Http().request.mock_calls)) + + class GerritUtilTest(unittest.TestCase): def setUp(self): super(GerritUtilTest, self).setUp()