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