1
0

test_lifespan.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. """Test cases for the Starlette lifespan integration."""
  2. from collections.abc import Generator
  3. import pytest
  4. from selenium.webdriver.common.by import By
  5. from reflex.testing import AppHarness
  6. from .utils import SessionStorage
  7. def LifespanApp():
  8. """App with lifespan tasks and context."""
  9. import asyncio
  10. from contextlib import asynccontextmanager
  11. import reflex as rx
  12. lifespan_task_global = 0
  13. lifespan_context_global = 0
  14. @asynccontextmanager
  15. async def lifespan_context(app, inc: int = 1):
  16. global lifespan_context_global
  17. print(f"Lifespan context entered: {app}.")
  18. lifespan_context_global += inc # pyright: ignore[reportUnboundVariable]
  19. try:
  20. yield
  21. finally:
  22. print("Lifespan context exited.")
  23. lifespan_context_global += inc
  24. async def lifespan_task(inc: int = 1):
  25. global lifespan_task_global
  26. print("Lifespan global started.")
  27. try:
  28. while True:
  29. lifespan_task_global += inc # pyright: ignore[reportUnboundVariable, reportPossiblyUnboundVariable]
  30. await asyncio.sleep(0.1)
  31. except asyncio.CancelledError as ce:
  32. print(f"Lifespan global cancelled: {ce}.")
  33. lifespan_task_global = 0
  34. class LifespanState(rx.State):
  35. interval: int = 100
  36. @rx.var(cache=False)
  37. def task_global(self) -> int:
  38. return lifespan_task_global
  39. @rx.var(cache=False)
  40. def context_global(self) -> int:
  41. return lifespan_context_global
  42. @rx.event
  43. def tick(self, date):
  44. pass
  45. def index():
  46. return rx.vstack(
  47. rx.text(LifespanState.task_global, id="task_global"),
  48. rx.text(LifespanState.context_global, id="context_global"),
  49. rx.button(
  50. rx.moment(
  51. interval=LifespanState.interval, on_change=LifespanState.tick
  52. ),
  53. on_click=LifespanState.set_interval( # pyright: ignore [reportAttributeAccessIssue]
  54. rx.cond(LifespanState.interval, 0, 100)
  55. ),
  56. id="toggle-tick",
  57. ),
  58. )
  59. app = rx.App()
  60. app.register_lifespan_task(lifespan_task)
  61. app.register_lifespan_task(lifespan_context, inc=2)
  62. app.add_page(index)
  63. @pytest.fixture()
  64. def lifespan_app(tmp_path) -> Generator[AppHarness, None, None]:
  65. """Start LifespanApp app at tmp_path via AppHarness.
  66. Args:
  67. tmp_path: pytest tmp_path fixture
  68. Yields:
  69. running AppHarness instance
  70. """
  71. with AppHarness.create(
  72. root=tmp_path,
  73. app_source=LifespanApp,
  74. ) as harness:
  75. yield harness
  76. @pytest.mark.asyncio
  77. async def test_lifespan(lifespan_app: AppHarness):
  78. """Test the lifespan integration.
  79. Args:
  80. lifespan_app: harness for LifespanApp app
  81. """
  82. assert lifespan_app.app_module is not None, "app module is not found"
  83. assert lifespan_app.app_instance is not None, "app is not running"
  84. driver = lifespan_app.frontend()
  85. ss = SessionStorage(driver)
  86. assert AppHarness._poll_for(lambda: ss.get("token") is not None), "token not found"
  87. context_global = driver.find_element(By.ID, "context_global")
  88. task_global = driver.find_element(By.ID, "task_global")
  89. assert context_global.text == "2"
  90. assert lifespan_app.app_module.lifespan_context_global == 2
  91. original_task_global_text = task_global.text
  92. original_task_global_value = int(original_task_global_text)
  93. lifespan_app.poll_for_content(task_global, exp_not_equal=original_task_global_text)
  94. driver.find_element(By.ID, "toggle-tick").click() # avoid teardown errors
  95. assert lifespan_app.app_module.lifespan_task_global > original_task_global_value
  96. assert int(task_global.text) > original_task_global_value
  97. # Kill the backend
  98. assert lifespan_app.backend is not None
  99. lifespan_app.backend.should_exit = True
  100. if lifespan_app.backend_thread is not None:
  101. lifespan_app.backend_thread.join()
  102. # Check that the lifespan tasks have been cancelled
  103. assert lifespan_app.app_module.lifespan_task_global == 0
  104. assert lifespan_app.app_module.lifespan_context_global == 4