python_plugin_test.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. # Copyright 2015, Google Inc.
  2. # All rights reserved.
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions are
  6. # met:
  7. #
  8. # * Redistributions of source code must retain the above copyright
  9. # notice, this list of conditions and the following disclaimer.
  10. # * Redistributions in binary form must reproduce the above
  11. # copyright notice, this list of conditions and the following disclaimer
  12. # in the documentation and/or other materials provided with the
  13. # distribution.
  14. # * Neither the name of Google Inc. nor the names of its
  15. # contributors may be used to endorse or promote products derived from
  16. # this software without specific prior written permission.
  17. #
  18. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. import argparse
  30. import contextlib
  31. import distutils.spawn
  32. import errno
  33. import itertools
  34. import os
  35. import pkg_resources
  36. import shutil
  37. import subprocess
  38. import sys
  39. import tempfile
  40. import threading
  41. import time
  42. import unittest
  43. from grpc.framework.alpha import exceptions
  44. from grpc.framework.foundation import future
  45. # Identifiers of entities we expect to find in the generated module.
  46. SERVICER_IDENTIFIER = 'EarlyAdopterTestServiceServicer'
  47. SERVER_IDENTIFIER = 'EarlyAdopterTestServiceServer'
  48. STUB_IDENTIFIER = 'EarlyAdopterTestServiceStub'
  49. SERVER_FACTORY_IDENTIFIER = 'early_adopter_create_TestService_server'
  50. STUB_FACTORY_IDENTIFIER = 'early_adopter_create_TestService_stub'
  51. # The timeout used in tests of RPCs that are supposed to expire.
  52. SHORT_TIMEOUT = 2
  53. # The timeout used in tests of RPCs that are not supposed to expire. The
  54. # absurdly large value doesn't matter since no passing execution of this test
  55. # module will ever wait the duration.
  56. LONG_TIMEOUT = 600
  57. NO_DELAY = 0
  58. class _ServicerMethods(object):
  59. def __init__(self, test_pb2, delay):
  60. self._condition = threading.Condition()
  61. self._delay = delay
  62. self._paused = False
  63. self._fail = False
  64. self._test_pb2 = test_pb2
  65. @contextlib.contextmanager
  66. def pause(self): # pylint: disable=invalid-name
  67. with self._condition:
  68. self._paused = True
  69. yield
  70. with self._condition:
  71. self._paused = False
  72. self._condition.notify_all()
  73. @contextlib.contextmanager
  74. def fail(self): # pylint: disable=invalid-name
  75. with self._condition:
  76. self._fail = True
  77. yield
  78. with self._condition:
  79. self._fail = False
  80. def _control(self): # pylint: disable=invalid-name
  81. with self._condition:
  82. if self._fail:
  83. raise ValueError()
  84. while self._paused:
  85. self._condition.wait()
  86. time.sleep(self._delay)
  87. def UnaryCall(self, request, unused_rpc_context):
  88. response = self._test_pb2.SimpleResponse()
  89. response.payload.payload_type = self._test_pb2.COMPRESSABLE
  90. response.payload.payload_compressable = 'a' * request.response_size
  91. self._control()
  92. return response
  93. def StreamingOutputCall(self, request, unused_rpc_context):
  94. for parameter in request.response_parameters:
  95. response = self._test_pb2.StreamingOutputCallResponse()
  96. response.payload.payload_type = self._test_pb2.COMPRESSABLE
  97. response.payload.payload_compressable = 'a' * parameter.size
  98. self._control()
  99. yield response
  100. def StreamingInputCall(self, request_iter, unused_rpc_context):
  101. response = self._test_pb2.StreamingInputCallResponse()
  102. aggregated_payload_size = 0
  103. for request in request_iter:
  104. aggregated_payload_size += len(request.payload.payload_compressable)
  105. response.aggregated_payload_size = aggregated_payload_size
  106. self._control()
  107. return response
  108. def FullDuplexCall(self, request_iter, unused_rpc_context):
  109. for request in request_iter:
  110. for parameter in request.response_parameters:
  111. response = self._test_pb2.StreamingOutputCallResponse()
  112. response.payload.payload_type = self._test_pb2.COMPRESSABLE
  113. response.payload.payload_compressable = 'a' * parameter.size
  114. self._control()
  115. yield response
  116. def HalfDuplexCall(self, request_iter, unused_rpc_context):
  117. responses = []
  118. for request in request_iter:
  119. for parameter in request.response_parameters:
  120. response = self._test_pb2.StreamingOutputCallResponse()
  121. response.payload.payload_type = self._test_pb2.COMPRESSABLE
  122. response.payload.payload_compressable = 'a' * parameter.size
  123. self._control()
  124. responses.append(response)
  125. for response in responses:
  126. yield response
  127. @contextlib.contextmanager
  128. def _CreateService(test_pb2, delay):
  129. """Provides a servicer backend and a stub.
  130. The servicer is just the implementation
  131. of the actual servicer passed to the face player of the python RPC
  132. implementation; the two are detached.
  133. Non-zero delay puts a delay on each call to the servicer, representative of
  134. communication latency. Timeout is the default timeout for the stub while
  135. waiting for the service.
  136. Args:
  137. test_pb2: The test_pb2 module generated by this test.
  138. delay: Delay in seconds per response from the servicer.
  139. Yields:
  140. A (servicer_methods, servicer, stub) three-tuple where servicer_methods is
  141. the back-end of the service bound to the stub and the server and stub
  142. are both activated and ready for use.
  143. """
  144. servicer_methods = _ServicerMethods(test_pb2, delay)
  145. class Servicer(getattr(test_pb2, SERVICER_IDENTIFIER)):
  146. def UnaryCall(self, request, context):
  147. return servicer_methods.UnaryCall(request, context)
  148. def StreamingOutputCall(self, request, context):
  149. return servicer_methods.StreamingOutputCall(request, context)
  150. def StreamingInputCall(self, request_iter, context):
  151. return servicer_methods.StreamingInputCall(request_iter, context)
  152. def FullDuplexCall(self, request_iter, context):
  153. return servicer_methods.FullDuplexCall(request_iter, context)
  154. def HalfDuplexCall(self, request_iter, context):
  155. return servicer_methods.HalfDuplexCall(request_iter, context)
  156. servicer = Servicer()
  157. server = getattr(
  158. test_pb2, SERVER_FACTORY_IDENTIFIER)(servicer, 0)
  159. with server:
  160. port = server.port()
  161. stub = getattr(test_pb2, STUB_FACTORY_IDENTIFIER)('localhost', port)
  162. with stub:
  163. yield servicer_methods, stub, server
  164. def _streaming_input_request_iterator(test_pb2):
  165. for _ in range(3):
  166. request = test_pb2.StreamingInputCallRequest()
  167. request.payload.payload_type = test_pb2.COMPRESSABLE
  168. request.payload.payload_compressable = 'a'
  169. yield request
  170. def _streaming_output_request(test_pb2):
  171. request = test_pb2.StreamingOutputCallRequest()
  172. sizes = [1, 2, 3]
  173. request.response_parameters.add(size=sizes[0], interval_us=0)
  174. request.response_parameters.add(size=sizes[1], interval_us=0)
  175. request.response_parameters.add(size=sizes[2], interval_us=0)
  176. return request
  177. def _full_duplex_request_iterator(test_pb2):
  178. request = test_pb2.StreamingOutputCallRequest()
  179. request.response_parameters.add(size=1, interval_us=0)
  180. yield request
  181. request = test_pb2.StreamingOutputCallRequest()
  182. request.response_parameters.add(size=2, interval_us=0)
  183. request.response_parameters.add(size=3, interval_us=0)
  184. yield request
  185. class PythonPluginTest(unittest.TestCase):
  186. """Test case for the gRPC Python protoc-plugin.
  187. While reading these tests, remember that the futures API
  188. (`stub.method.async()`) only gives futures for the *non-streaming* responses,
  189. else it behaves like its blocking cousin.
  190. """
  191. def setUp(self):
  192. # Assume that the appropriate protoc and grpc_python_plugins are on the
  193. # path.
  194. protoc_command = 'protoc'
  195. protoc_plugin_filename = distutils.spawn.find_executable(
  196. 'grpc_python_plugin')
  197. test_proto_filename = pkg_resources.resource_filename(
  198. 'grpc_protoc_plugin', 'test.proto')
  199. if not os.path.isfile(protoc_command):
  200. # Assume that if we haven't built protoc that it's on the system.
  201. protoc_command = 'protoc'
  202. # Ensure that the output directory exists.
  203. self.outdir = tempfile.mkdtemp()
  204. # Invoke protoc with the plugin.
  205. cmd = [
  206. protoc_command,
  207. '--plugin=protoc-gen-python-grpc=%s' % protoc_plugin_filename,
  208. '-I .',
  209. '--python_out=%s' % self.outdir,
  210. '--python-grpc_out=%s' % self.outdir,
  211. os.path.basename(test_proto_filename),
  212. ]
  213. subprocess.check_call(' '.join(cmd), shell=True, env=os.environ,
  214. cwd=os.path.dirname(test_proto_filename))
  215. sys.path.append(self.outdir)
  216. def tearDown(self):
  217. try:
  218. shutil.rmtree(self.outdir)
  219. except OSError as exc:
  220. if exc.errno != errno.ENOENT:
  221. raise
  222. # TODO(atash): Figure out which of these tests is hanging flakily with small
  223. # probability.
  224. def testImportAttributes(self):
  225. # check that we can access the generated module and its members.
  226. import test_pb2 # pylint: disable=g-import-not-at-top
  227. self.assertIsNotNone(getattr(test_pb2, SERVICER_IDENTIFIER, None))
  228. self.assertIsNotNone(getattr(test_pb2, SERVER_IDENTIFIER, None))
  229. self.assertIsNotNone(getattr(test_pb2, STUB_IDENTIFIER, None))
  230. self.assertIsNotNone(getattr(test_pb2, SERVER_FACTORY_IDENTIFIER, None))
  231. self.assertIsNotNone(getattr(test_pb2, STUB_FACTORY_IDENTIFIER, None))
  232. def testUpDown(self):
  233. import test_pb2
  234. with _CreateService(
  235. test_pb2, NO_DELAY) as (servicer, stub, unused_server):
  236. request = test_pb2.SimpleRequest(response_size=13)
  237. def testUnaryCall(self):
  238. import test_pb2 # pylint: disable=g-import-not-at-top
  239. with _CreateService(test_pb2, NO_DELAY) as (methods, stub, unused_server):
  240. timeout = 6 # TODO(issue 2039): LONG_TIMEOUT like the other methods.
  241. request = test_pb2.SimpleRequest(response_size=13)
  242. response = stub.UnaryCall(request, timeout)
  243. expected_response = methods.UnaryCall(request, 'not a real RpcContext!')
  244. self.assertEqual(expected_response, response)
  245. def testUnaryCallAsync(self):
  246. import test_pb2 # pylint: disable=g-import-not-at-top
  247. request = test_pb2.SimpleRequest(response_size=13)
  248. with _CreateService(test_pb2, NO_DELAY) as (
  249. methods, stub, unused_server):
  250. # Check that the call does not block waiting for the server to respond.
  251. with methods.pause():
  252. response_future = stub.UnaryCall.async(request, LONG_TIMEOUT)
  253. response = response_future.result()
  254. expected_response = methods.UnaryCall(request, 'not a real RpcContext!')
  255. self.assertEqual(expected_response, response)
  256. def testUnaryCallAsyncExpired(self):
  257. import test_pb2 # pylint: disable=g-import-not-at-top
  258. with _CreateService(test_pb2, NO_DELAY) as (
  259. methods, stub, unused_server):
  260. request = test_pb2.SimpleRequest(response_size=13)
  261. with methods.pause():
  262. response_future = stub.UnaryCall.async(request, SHORT_TIMEOUT)
  263. with self.assertRaises(exceptions.ExpirationError):
  264. response_future.result()
  265. @unittest.skip('TODO(atash,nathaniel): figure out why this flakily hangs '
  266. 'forever and fix.')
  267. def testUnaryCallAsyncCancelled(self):
  268. import test_pb2 # pylint: disable=g-import-not-at-top
  269. request = test_pb2.SimpleRequest(response_size=13)
  270. with _CreateService(test_pb2, NO_DELAY) as (
  271. methods, stub, unused_server):
  272. with methods.pause():
  273. response_future = stub.UnaryCall.async(request, 1)
  274. response_future.cancel()
  275. self.assertTrue(response_future.cancelled())
  276. def testUnaryCallAsyncFailed(self):
  277. import test_pb2 # pylint: disable=g-import-not-at-top
  278. request = test_pb2.SimpleRequest(response_size=13)
  279. with _CreateService(test_pb2, NO_DELAY) as (
  280. methods, stub, unused_server):
  281. with methods.fail():
  282. response_future = stub.UnaryCall.async(request, LONG_TIMEOUT)
  283. self.assertIsNotNone(response_future.exception())
  284. def testStreamingOutputCall(self):
  285. import test_pb2 # pylint: disable=g-import-not-at-top
  286. request = _streaming_output_request(test_pb2)
  287. with _CreateService(test_pb2, NO_DELAY) as (methods, stub, unused_server):
  288. responses = stub.StreamingOutputCall(request, LONG_TIMEOUT)
  289. expected_responses = methods.StreamingOutputCall(
  290. request, 'not a real RpcContext!')
  291. for expected_response, response in itertools.izip_longest(
  292. expected_responses, responses):
  293. self.assertEqual(expected_response, response)
  294. @unittest.skip('TODO(atash,nathaniel): figure out why this flakily hangs '
  295. 'forever and fix.')
  296. def testStreamingOutputCallExpired(self):
  297. import test_pb2 # pylint: disable=g-import-not-at-top
  298. request = _streaming_output_request(test_pb2)
  299. with _CreateService(test_pb2, NO_DELAY) as (
  300. methods, stub, unused_server):
  301. with methods.pause():
  302. responses = stub.StreamingOutputCall(request, SHORT_TIMEOUT)
  303. with self.assertRaises(exceptions.ExpirationError):
  304. list(responses)
  305. @unittest.skip('TODO(atash,nathaniel): figure out why this flakily hangs '
  306. 'forever and fix.')
  307. def testStreamingOutputCallCancelled(self):
  308. import test_pb2 # pylint: disable=g-import-not-at-top
  309. request = _streaming_output_request(test_pb2)
  310. with _CreateService(test_pb2, NO_DELAY) as (
  311. unused_methods, stub, unused_server):
  312. responses = stub.StreamingOutputCall(request, SHORT_TIMEOUT)
  313. next(responses)
  314. responses.cancel()
  315. with self.assertRaises(future.CancelledError):
  316. next(responses)
  317. @unittest.skip('TODO(atash,nathaniel): figure out why this times out '
  318. 'instead of raising the proper error.')
  319. def testStreamingOutputCallFailed(self):
  320. import test_pb2 # pylint: disable=g-import-not-at-top
  321. request = _streaming_output_request(test_pb2)
  322. with _CreateService(test_pb2, NO_DELAY) as (
  323. methods, stub, unused_server):
  324. with methods.fail():
  325. responses = stub.StreamingOutputCall(request, 1)
  326. self.assertIsNotNone(responses)
  327. with self.assertRaises(exceptions.ServicerError):
  328. next(responses)
  329. @unittest.skip('TODO(atash,nathaniel): figure out why this flakily hangs '
  330. 'forever and fix.')
  331. def testStreamingInputCall(self):
  332. import test_pb2 # pylint: disable=g-import-not-at-top
  333. with _CreateService(test_pb2, NO_DELAY) as (methods, stub, unused_server):
  334. response = stub.StreamingInputCall(
  335. _streaming_input_request_iterator(test_pb2), LONG_TIMEOUT)
  336. expected_response = methods.StreamingInputCall(
  337. _streaming_input_request_iterator(test_pb2), 'not a real RpcContext!')
  338. self.assertEqual(expected_response, response)
  339. def testStreamingInputCallAsync(self):
  340. import test_pb2 # pylint: disable=g-import-not-at-top
  341. with _CreateService(test_pb2, NO_DELAY) as (
  342. methods, stub, unused_server):
  343. with methods.pause():
  344. response_future = stub.StreamingInputCall.async(
  345. _streaming_input_request_iterator(test_pb2), LONG_TIMEOUT)
  346. response = response_future.result()
  347. expected_response = methods.StreamingInputCall(
  348. _streaming_input_request_iterator(test_pb2), 'not a real RpcContext!')
  349. self.assertEqual(expected_response, response)
  350. def testStreamingInputCallAsyncExpired(self):
  351. import test_pb2 # pylint: disable=g-import-not-at-top
  352. with _CreateService(test_pb2, NO_DELAY) as (
  353. methods, stub, unused_server):
  354. with methods.pause():
  355. response_future = stub.StreamingInputCall.async(
  356. _streaming_input_request_iterator(test_pb2), SHORT_TIMEOUT)
  357. with self.assertRaises(exceptions.ExpirationError):
  358. response_future.result()
  359. self.assertIsInstance(
  360. response_future.exception(), exceptions.ExpirationError)
  361. def testStreamingInputCallAsyncCancelled(self):
  362. import test_pb2 # pylint: disable=g-import-not-at-top
  363. with _CreateService(test_pb2, NO_DELAY) as (
  364. methods, stub, unused_server):
  365. with methods.pause():
  366. timeout = 6 # TODO(issue 2039): LONG_TIMEOUT like the other methods.
  367. response_future = stub.StreamingInputCall.async(
  368. _streaming_input_request_iterator(test_pb2), timeout)
  369. response_future.cancel()
  370. self.assertTrue(response_future.cancelled())
  371. with self.assertRaises(future.CancelledError):
  372. response_future.result()
  373. def testStreamingInputCallAsyncFailed(self):
  374. import test_pb2 # pylint: disable=g-import-not-at-top
  375. with _CreateService(test_pb2, NO_DELAY) as (
  376. methods, stub, unused_server):
  377. with methods.fail():
  378. response_future = stub.StreamingInputCall.async(
  379. _streaming_input_request_iterator(test_pb2), SHORT_TIMEOUT)
  380. self.assertIsNotNone(response_future.exception())
  381. def testFullDuplexCall(self):
  382. import test_pb2 # pylint: disable=g-import-not-at-top
  383. with _CreateService(test_pb2, NO_DELAY) as (methods, stub, unused_server):
  384. responses = stub.FullDuplexCall(
  385. _full_duplex_request_iterator(test_pb2), LONG_TIMEOUT)
  386. expected_responses = methods.FullDuplexCall(
  387. _full_duplex_request_iterator(test_pb2), 'not a real RpcContext!')
  388. for expected_response, response in itertools.izip_longest(
  389. expected_responses, responses):
  390. self.assertEqual(expected_response, response)
  391. @unittest.skip('TODO(atash,nathaniel): figure out why this flakily hangs '
  392. 'forever and fix.')
  393. def testFullDuplexCallExpired(self):
  394. import test_pb2 # pylint: disable=g-import-not-at-top
  395. request_iterator = _full_duplex_request_iterator(test_pb2)
  396. with _CreateService(test_pb2, NO_DELAY) as (
  397. methods, stub, unused_server):
  398. with methods.pause():
  399. responses = stub.FullDuplexCall(request_iterator, SHORT_TIMEOUT)
  400. with self.assertRaises(exceptions.ExpirationError):
  401. list(responses)
  402. @unittest.skip('TODO(atash,nathaniel): figure out why this flakily hangs '
  403. 'forever and fix.')
  404. def testFullDuplexCallCancelled(self):
  405. import test_pb2 # pylint: disable=g-import-not-at-top
  406. with _CreateService(test_pb2, NO_DELAY) as (methods, stub, unused_server):
  407. request_iterator = _full_duplex_request_iterator(test_pb2)
  408. responses = stub.FullDuplexCall(request_iterator, LONG_TIMEOUT)
  409. next(responses)
  410. responses.cancel()
  411. with self.assertRaises(future.CancelledError):
  412. next(responses)
  413. @unittest.skip('TODO(atash,nathaniel): figure out why this hangs forever '
  414. 'and fix.')
  415. def testFullDuplexCallFailed(self):
  416. import test_pb2 # pylint: disable=g-import-not-at-top
  417. request_iterator = _full_duplex_request_iterator(test_pb2)
  418. with _CreateService(test_pb2, NO_DELAY) as (
  419. methods, stub, unused_server):
  420. with methods.fail():
  421. responses = stub.FullDuplexCall(request_iterator, LONG_TIMEOUT)
  422. self.assertIsNotNone(responses)
  423. with self.assertRaises(exceptions.ServicerError):
  424. next(responses)
  425. @unittest.skip('TODO(atash,nathaniel): figure out why this flakily hangs '
  426. 'forever and fix.')
  427. def testHalfDuplexCall(self):
  428. import test_pb2 # pylint: disable=g-import-not-at-top
  429. with _CreateService(test_pb2, NO_DELAY) as (
  430. methods, stub, unused_server):
  431. def half_duplex_request_iterator():
  432. request = test_pb2.StreamingOutputCallRequest()
  433. request.response_parameters.add(size=1, interval_us=0)
  434. yield request
  435. request = test_pb2.StreamingOutputCallRequest()
  436. request.response_parameters.add(size=2, interval_us=0)
  437. request.response_parameters.add(size=3, interval_us=0)
  438. yield request
  439. responses = stub.HalfDuplexCall(
  440. half_duplex_request_iterator(), LONG_TIMEOUT)
  441. expected_responses = methods.HalfDuplexCall(
  442. half_duplex_request_iterator(), 'not a real RpcContext!')
  443. for check in itertools.izip_longest(expected_responses, responses):
  444. expected_response, response = check
  445. self.assertEqual(expected_response, response)
  446. def testHalfDuplexCallWedged(self):
  447. import test_pb2 # pylint: disable=g-import-not-at-top
  448. condition = threading.Condition()
  449. wait_cell = [False]
  450. @contextlib.contextmanager
  451. def wait(): # pylint: disable=invalid-name
  452. # Where's Python 3's 'nonlocal' statement when you need it?
  453. with condition:
  454. wait_cell[0] = True
  455. yield
  456. with condition:
  457. wait_cell[0] = False
  458. condition.notify_all()
  459. def half_duplex_request_iterator():
  460. request = test_pb2.StreamingOutputCallRequest()
  461. request.response_parameters.add(size=1, interval_us=0)
  462. yield request
  463. with condition:
  464. while wait_cell[0]:
  465. condition.wait()
  466. with _CreateService(test_pb2, NO_DELAY) as (methods, stub, unused_server):
  467. with wait():
  468. responses = stub.HalfDuplexCall(
  469. half_duplex_request_iterator(), SHORT_TIMEOUT)
  470. # half-duplex waits for the client to send all info
  471. with self.assertRaises(exceptions.ExpirationError):
  472. next(responses)
  473. if __name__ == '__main__':
  474. os.chdir(os.path.dirname(sys.argv[0]))
  475. unittest.main(verbosity=2)