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 |