| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2016 The LUCI Authors. All rights reserved. | 2 # Copyright 2016 The LUCI Authors. All rights reserved. |
| 3 # Use of this source code is governed under the Apache License, Version 2.0 | 3 # Use of this source code is governed under the Apache License, Version 2.0 |
| 4 # that can be found in the LICENSE file. | 4 # that can be found in the LICENSE file. |
| 5 | 5 |
| 6 import logging | 6 import logging |
| 7 import sys | 7 import sys |
| 8 import threading | 8 import threading |
| 9 import time | 9 import time |
| 10 import unittest | 10 import unittest |
| 11 | 11 |
| 12 import test_env_bot_code | 12 import test_env_bot_code |
| 13 test_env_bot_code.setup_test_env() | 13 test_env_bot_code.setup_test_env() |
| 14 | 14 |
| 15 from depot_tools import auto_stub | 15 from depot_tools import auto_stub |
| 16 | 16 import remote_client_grpc |
| 17 try: | |
| 18 import remote_client_grpc | |
| 19 except ImportError as e: | |
| 20 print('Could not import gRPC remote client, likely due to missing grpc ' | |
| 21 'library. Skipping tests.') | |
| 22 sys.exit(0) | |
| 23 | 17 |
| 24 | 18 |
| 25 class FakeBotServiceStub(object): | 19 class FakeGrpcProxy(object): |
| 26 def __init__(self, testobj): | 20 def __init__(self, testobj): |
| 27 self._testobj = testobj | 21 self._testobj = testobj |
| 28 | 22 |
| 29 def TaskUpdate(self, request, **_kwargs): | 23 def call_unary(self, name, request): |
| 30 return self._testobj._handle_call('TaskUpdate', request) | 24 return self._testobj._handle_call(name, request) |
| 31 | |
| 32 def Handshake(self, request, **_kwargs): | |
| 33 return self._testobj._handle_call('Handshake', request) | |
| 34 | |
| 35 def Poll(self, request, **_kwargs): | |
| 36 return self._testobj._handle_call('Poll', request) | |
| 37 | |
| 38 | |
| 39 # If gRPC isn't successfully imported, this will be seen as a nonstandard | |
| 40 # exception because it won't appear to be derived from Exception. This | |
| 41 # only affects PyLint because the test will never be run if gRPC import | |
| 42 # fails. | |
| 43 # pylint: disable=W0710 | |
| 44 class FakeGrpcError(remote_client_grpc.grpc.RpcError): | |
| 45 """Duplicates a basic UNAVAILABLE error""" | |
| 46 def __init__(self, code): | |
| 47 self._code = code | |
| 48 super(FakeGrpcError, self).__init__('something terrible happened') | |
| 49 | |
| 50 def code(self): | |
| 51 return self._code | |
| 52 | 25 |
| 53 | 26 |
| 54 class TestRemoteClientGrpc(auto_stub.TestCase): | 27 class TestRemoteClientGrpc(auto_stub.TestCase): |
| 55 def setUp(self): | 28 def setUp(self): |
| 56 super(TestRemoteClientGrpc, self).setUp() | 29 super(TestRemoteClientGrpc, self).setUp() |
| 57 self._num_sleeps = 0 | 30 self._num_sleeps = 0 |
| 58 def fake_sleep(_time): | 31 def fake_sleep(_time): |
| 59 self._num_sleeps += 1 | 32 self._num_sleeps += 1 |
| 60 self.mock(time, 'sleep', fake_sleep) | 33 self.mock(time, 'sleep', fake_sleep) |
| 61 self._client = remote_client_grpc.RemoteClientGrpc('1.2.3.4:90') | 34 self._client = remote_client_grpc.RemoteClientGrpc('1.2.3.4:90', |
| 62 self._client._stub = FakeBotServiceStub(self) | 35 FakeGrpcProxy(self)) |
| 63 self._expected = [] | 36 self._expected = [] |
| 64 self._error_codes = [] | 37 self._error_codes = [] |
| 65 | 38 |
| 66 def _handle_call(self, method, request): | 39 def _handle_call(self, method, request): |
| 67 """This is called by FakeBotServiceStub to implement fake calls""" | 40 """This is called by FakeGrpcProxy to implement fake calls""" |
| 68 if len(self._error_codes) > 0: | |
| 69 code, self._error_codes = self._error_codes[0], self._error_codes[1:] | |
| 70 raise FakeGrpcError(code) | |
| 71 | |
| 72 if self._error_codes: | |
| 73 text = self._error_codes | |
| 74 self._error_codes = '' | |
| 75 raise FakeGrpcError(text) | |
| 76 | |
| 77 # Pop off the first item on the list | 41 # Pop off the first item on the list |
| 78 self.assertTrue(len(self._expected) > 0) | 42 self.assertTrue(len(self._expected) > 0) |
| 79 expected, self._expected = self._expected[0], self._expected[1:] | 43 expected, self._expected = self._expected[0], self._expected[1:] |
| 80 # Each element of the "expected" array should be a 3-tuple: | 44 # Each element of the "expected" array should be a 3-tuple: |
| 81 # * The name of the method (eg 'TaskUpdate') | 45 # * The name of the method (eg 'TaskUpdate') |
| 82 # * The proto request | 46 # * The proto request |
| 83 # * The proto response | 47 # * The proto response |
| 84 self.assertEqual(method, expected[0]) | 48 self.assertEqual(method, expected[0]) |
| 85 self.assertEqual(request, expected[1]) | 49 self.assertEqual(request, expected[1]) |
| 86 return expected[2] | 50 return expected[2] |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 147 'server_version': u'101', | 111 'server_version': u'101', |
| 148 'bot_version': u'102', | 112 'bot_version': u'102', |
| 149 'bot_group_cfg_version': u'', | 113 'bot_group_cfg_version': u'', |
| 150 'bot_group_cfg': { | 114 'bot_group_cfg': { |
| 151 'dimensions': { | 115 'dimensions': { |
| 152 u'mammal': [u'kangaroo', u'emu'], | 116 u'mammal': [u'kangaroo', u'emu'], |
| 153 }, | 117 }, |
| 154 }, | 118 }, |
| 155 }) | 119 }) |
| 156 | 120 |
| 157 def test_handshake_grpc_unavailable(self): | |
| 158 """Ensures that the handshake function sleeps after a gRPC error""" | |
| 159 msg_req = remote_client_grpc.swarming_bot_pb2.HandshakeRequest() | |
| 160 msg_req.attributes.CopyFrom(self.get_bot_attributes_proto()) | |
| 161 | |
| 162 # Create proto response | |
| 163 msg_rsp = remote_client_grpc.swarming_bot_pb2.HandshakeResponse() | |
| 164 msg_rsp.server_version = '101' | |
| 165 msg_rsp.bot_version = '102' | |
| 166 d1 = msg_rsp.bot_group_cfg.dimensions.add() | |
| 167 d1.name = 'mammal' | |
| 168 d1.values.extend(['kangaroo', 'emu']) | |
| 169 | |
| 170 # Execute call and verify response | |
| 171 expected_call = ('Handshake', msg_req, msg_rsp) | |
| 172 self._expected.append(expected_call) | |
| 173 self._error_codes.append(remote_client_grpc.grpc.StatusCode.UNAVAILABLE) | |
| 174 self._error_codes.append(remote_client_grpc.grpc.StatusCode.UNAVAILABLE) | |
| 175 response = self._client.do_handshake(self.get_bot_attributes_dict()) | |
| 176 self.assertEqual(self._num_sleeps, 2) | |
| 177 self.assertEqual(response, { | |
| 178 'server_version': u'101', | |
| 179 'bot_version': u'102', | |
| 180 'bot_group_cfg_version': u'', | |
| 181 'bot_group_cfg': { | |
| 182 'dimensions': { | |
| 183 u'mammal': [u'kangaroo', u'emu'], | |
| 184 }, | |
| 185 }, | |
| 186 }) | |
| 187 | |
| 188 def test_handshake_grpc_other_error(self): | |
| 189 """Ensures that the handshake function only catches UNAVAILABLE""" | |
| 190 self._error_codes.append(remote_client_grpc.grpc.StatusCode.UNAVAILABLE) | |
| 191 self._error_codes.append(remote_client_grpc.grpc.StatusCode.INTERNAL) | |
| 192 got_exception = None | |
| 193 try: | |
| 194 self._client.do_handshake(self.get_bot_attributes_dict()) | |
| 195 except remote_client_grpc.grpc.RpcError as g: | |
| 196 got_exception = g | |
| 197 self.assertEqual(got_exception.code(), | |
| 198 remote_client_grpc.grpc.StatusCode.INTERNAL) | |
| 199 self.assertEqual(self._num_sleeps, 1) | |
| 200 | |
| 201 def test_handshake_grpc_too_many_errors(self): | |
| 202 """Ensures that the handshake function only catches UNAVAILABLE""" | |
| 203 self._error_codes.append(remote_client_grpc.grpc.StatusCode.UNAVAILABLE) | |
| 204 self._error_codes.append(remote_client_grpc.grpc.StatusCode.UNAVAILABLE) | |
| 205 self._error_codes.append(remote_client_grpc.grpc.StatusCode.UNAVAILABLE) | |
| 206 self.mock(remote_client_grpc, 'MAX_GRPC_ATTEMPTS', 2) | |
| 207 with self.assertRaises(remote_client_grpc.grpc.RpcError) as g: | |
| 208 self._client.do_handshake(self.get_bot_attributes_dict()) | |
| 209 self.assertEqual(self._num_sleeps, 2) | |
| 210 self.assertEqual(g.exception.code(), | |
| 211 remote_client_grpc.grpc.StatusCode.UNAVAILABLE) | |
| 212 | |
| 213 def test_poll_manifest(self): | 121 def test_poll_manifest(self): |
| 214 """Verifies that we can generate a reasonable manifest from a proto""" | 122 """Verifies that we can generate a reasonable manifest from a proto""" |
| 215 msg_req = remote_client_grpc.swarming_bot_pb2.PollRequest() | 123 msg_req = remote_client_grpc.swarming_bot_pb2.PollRequest() |
| 216 msg_req.attributes.CopyFrom(self.get_bot_attributes_proto()) | 124 msg_req.attributes.CopyFrom(self.get_bot_attributes_proto()) |
| 217 | 125 |
| 218 # Create dict response | 126 # Create dict response |
| 219 dict_rsp = { | 127 dict_rsp = { |
| 220 'bot_id': u'baby_bot', | 128 'bot_id': u'baby_bot', |
| 221 'command': None, | 129 'command': None, |
| 222 'dimensions': { | 130 'dimensions': { |
| (...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 330 self._expected.append(expected_call) | 238 self._expected.append(expected_call) |
| 331 response = self._client.post_task_update('abc123', 'baby_bot', params, | 239 response = self._client.post_task_update('abc123', 'baby_bot', params, |
| 332 ['abc', 7], -5) | 240 ['abc', 7], -5) |
| 333 self.assertTrue(response) | 241 self.assertTrue(response) |
| 334 | 242 |
| 335 if __name__ == '__main__': | 243 if __name__ == '__main__': |
| 336 logging.basicConfig( | 244 logging.basicConfig( |
| 337 level=logging.DEBUG if '-v' in sys.argv else logging.CRITICAL) | 245 level=logging.DEBUG if '-v' in sys.argv else logging.CRITICAL) |
| 338 unittest.TestCase.maxDiff = None | 246 unittest.TestCase.maxDiff = None |
| 339 unittest.main() | 247 unittest.main() |
| OLD | NEW |