/quarkchain/p2p/cancel_token/tests/test_cancel_token.py

https://github.com/QuarkChain/pyquarkchain · Python · 179 lines · 157 code · 20 blank · 2 comment · 2 complexity · 2aa09cd77b27581c6148b981fa07dbad MD5 · raw file

  1. import asyncio
  2. import functools
  3. import pytest
  4. from quarkchain.p2p.cancel_token.token import (
  5. CancelToken,
  6. EventLoopMismatch,
  7. OperationCancelled,
  8. )
  9. def test_token_single():
  10. token = CancelToken("token")
  11. assert not token.triggered
  12. token.trigger()
  13. assert token.triggered
  14. assert token.triggered_token == token
  15. def test_token_chain_event_loop_mismatch():
  16. token = CancelToken("token")
  17. token2 = CancelToken("token2", loop=asyncio.new_event_loop())
  18. with pytest.raises(EventLoopMismatch):
  19. token.chain(token2)
  20. def test_token_chain_trigger_chain():
  21. token = CancelToken("token")
  22. token2 = CancelToken("token2")
  23. token3 = CancelToken("token3")
  24. intermediate_chain = token.chain(token2)
  25. chain = intermediate_chain.chain(token3)
  26. assert not chain.triggered
  27. chain.trigger()
  28. assert chain.triggered
  29. assert not intermediate_chain.triggered
  30. assert chain.triggered_token == chain
  31. assert not token.triggered
  32. assert not token2.triggered
  33. assert not token3.triggered
  34. def test_token_chain_trigger_first():
  35. token = CancelToken("token")
  36. token2 = CancelToken("token2")
  37. token3 = CancelToken("token3")
  38. chain = token.chain(token2).chain(token3)
  39. assert not chain.triggered
  40. token.trigger()
  41. assert chain.triggered
  42. assert chain.triggered_token == token
  43. def test_token_chain_trigger_middle():
  44. token = CancelToken("token")
  45. token2 = CancelToken("token2")
  46. token3 = CancelToken("token3")
  47. intermediate_chain = token.chain(token2)
  48. chain = intermediate_chain.chain(token3)
  49. assert not chain.triggered
  50. token2.trigger()
  51. assert chain.triggered
  52. assert intermediate_chain.triggered
  53. assert chain.triggered_token == token2
  54. assert not token3.triggered
  55. assert not token.triggered
  56. def test_token_chain_trigger_last():
  57. token = CancelToken("token")
  58. token2 = CancelToken("token2")
  59. token3 = CancelToken("token3")
  60. intermediate_chain = token.chain(token2)
  61. chain = intermediate_chain.chain(token3)
  62. assert not chain.triggered
  63. token3.trigger()
  64. assert chain.triggered
  65. assert chain.triggered_token == token3
  66. assert not intermediate_chain.triggered
  67. @pytest.mark.asyncio
  68. async def test_token_wait(event_loop):
  69. token = CancelToken("token")
  70. event_loop.call_soon(token.trigger)
  71. done, pending = await asyncio.wait([token.wait()], timeout=0.1)
  72. assert len(done) == 1
  73. assert len(pending) == 0
  74. assert token.triggered
  75. @pytest.mark.asyncio
  76. async def test_wait_cancel_pending_tasks_on_completion(event_loop):
  77. token = CancelToken("token")
  78. token2 = CancelToken("token2")
  79. chain = token.chain(token2)
  80. event_loop.call_soon(token2.trigger)
  81. await chain.wait()
  82. await assert_only_current_task_not_done()
  83. @pytest.mark.asyncio
  84. async def test_wait_cancel_pending_tasks_on_cancellation(event_loop):
  85. """Test that cancelling a pending CancelToken.wait() coroutine doesn't leave .wait()
  86. coroutines for any chained tokens behind.
  87. """
  88. token = (
  89. CancelToken("token").chain(CancelToken("token2")).chain(CancelToken("token3"))
  90. )
  91. token_wait_coroutine = token.wait()
  92. done, pending = await asyncio.wait([token_wait_coroutine], timeout=0.1)
  93. assert len(done) == 0
  94. assert len(pending) == 1
  95. pending_task = pending.pop()
  96. assert pending_task._coro == token_wait_coroutine
  97. pending_task.cancel()
  98. await assert_only_current_task_not_done()
  99. @pytest.mark.asyncio
  100. async def test_cancellable_wait(event_loop):
  101. fut = asyncio.Future()
  102. event_loop.call_soon(functools.partial(fut.set_result, "result"))
  103. result = await CancelToken("token").cancellable_wait(fut, timeout=1)
  104. assert result == "result"
  105. await assert_only_current_task_not_done()
  106. @pytest.mark.asyncio
  107. async def test_cancellable_wait_future_exception(event_loop):
  108. fut = asyncio.Future()
  109. event_loop.call_soon(functools.partial(fut.set_exception, Exception()))
  110. with pytest.raises(Exception):
  111. await CancelToken("token").cancellable_wait(fut, timeout=1)
  112. await assert_only_current_task_not_done()
  113. @pytest.mark.asyncio
  114. async def test_cancellable_wait_cancels_subtasks_when_cancelled(event_loop):
  115. token = CancelToken("")
  116. future = asyncio.ensure_future(token.cancellable_wait(asyncio.sleep(2)))
  117. with pytest.raises(asyncio.TimeoutError):
  118. # asyncio.wait_for() will timeout and then cancel our cancellable_wait() future, but
  119. # Task.cancel() doesn't immediately cancels the task
  120. # (https://docs.python.org/3/library/asyncio-task.html#asyncio.Task.cancel), so we need
  121. # the sleep below before we check that the task is actually cancelled.
  122. await asyncio.wait_for(future, timeout=0.01)
  123. await asyncio.sleep(0)
  124. assert future.cancelled()
  125. await assert_only_current_task_not_done()
  126. @pytest.mark.asyncio
  127. async def test_cancellable_wait_timeout():
  128. with pytest.raises(TimeoutError):
  129. await CancelToken("token").cancellable_wait(asyncio.sleep(0.02), timeout=0.01)
  130. await assert_only_current_task_not_done()
  131. @pytest.mark.asyncio
  132. async def test_cancellable_wait_operation_cancelled(event_loop):
  133. token = CancelToken("token")
  134. token.trigger()
  135. with pytest.raises(OperationCancelled):
  136. await token.cancellable_wait(asyncio.sleep(0.02))
  137. await assert_only_current_task_not_done()
  138. async def assert_only_current_task_not_done():
  139. # This sleep() is necessary because Task.cancel() doesn't immediately cancels the task:
  140. # https://docs.python.org/3/library/asyncio-task.html#asyncio.Task.cancel
  141. await asyncio.sleep(0.01)
  142. for task in asyncio.Task.all_tasks():
  143. if task == asyncio.Task.current_task():
  144. # This is the task for this very test, so it will be running
  145. assert not task.done()
  146. else:
  147. assert task.done()