Skip to content

Commit c26671e

Browse files
committed
adding tests for async background callbacks
1 parent 503f7c0 commit c26671e

File tree

7 files changed

+324
-17
lines changed

7 files changed

+324
-17
lines changed

tests/integration/async_tests/__init__.py

Whitespace-only changes.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from dash import Dash, Input, Output, dcc, html
2+
import time
3+
4+
from tests.integration.background_callback.utils import get_background_callback_manager
5+
6+
background_callback_manager = get_background_callback_manager()
7+
handle = background_callback_manager.handle
8+
9+
app = Dash(__name__)
10+
app.layout = html.Div(
11+
[
12+
dcc.Input(id="input", value="initial value"),
13+
html.Div(html.Div([1.5, None, "string", html.Div(id="output-1")])),
14+
]
15+
)
16+
17+
18+
@app.callback(
19+
Output("output-1", "children"),
20+
[Input("input", "value")],
21+
interval=500,
22+
manager=background_callback_manager,
23+
background=True,
24+
)
25+
async def update_output(value):
26+
time.sleep(0.1)
27+
return value
28+
29+
30+
if __name__ == "__main__":
31+
app.run(debug=True)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from dash import Dash, Input, Output, html, callback, set_props
2+
import time
3+
4+
from tests.integration.background_callback.utils import get_background_callback_manager
5+
6+
background_callback_manager = get_background_callback_manager()
7+
handle = background_callback_manager.handle
8+
9+
app = Dash(__name__, background_callback_manager=background_callback_manager)
10+
app.test_lock = lock = background_callback_manager.test_lock
11+
12+
app.layout = html.Div(
13+
[
14+
html.Button("start", id="start"),
15+
html.Div(id="secondary"),
16+
html.Div(id="no-output"),
17+
html.Div("initial", id="output"),
18+
html.Button("start-no-output", id="start-no-output"),
19+
]
20+
)
21+
22+
23+
@callback(
24+
Output("output", "children"),
25+
Input("start", "n_clicks"),
26+
prevent_initial_call=True,
27+
background=True,
28+
interval=500,
29+
)
30+
async def on_click(_):
31+
set_props("secondary", {"children": "first"})
32+
set_props("secondary", {"style": {"background": "red"}})
33+
time.sleep(2)
34+
set_props("secondary", {"children": "second"})
35+
return "completed"
36+
37+
38+
@callback(
39+
Input("start-no-output", "n_clicks"),
40+
prevent_initial_call=True,
41+
background=True,
42+
)
43+
async def on_click(_):
44+
set_props("no-output", {"children": "started"})
45+
time.sleep(2)
46+
set_props("no-output", {"children": "completed"})
47+
48+
49+
if __name__ == "__main__":
50+
app.run(debug=True)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import os
2+
3+
import pytest
4+
5+
6+
if "REDIS_URL" in os.environ:
7+
managers = ["celery", "diskcache"]
8+
else:
9+
print("Skipping celery tests because REDIS_URL is not defined")
10+
managers = ["diskcache"]
11+
12+
13+
@pytest.fixture(params=managers)
14+
def manager(request):
15+
return request.param
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import sys
2+
3+
import pytest
4+
from flaky import flaky
5+
from multiprocessing import Lock
6+
from tests.integration.async_tests.utils import setup_background_callback_app
7+
import time
8+
9+
def test_001ab_arbitrary(dash_duo, manager):
10+
with setup_background_callback_app(manager, "app_arbitrary_async") as app:
11+
dash_duo.start_server(app)
12+
13+
dash_duo.wait_for_text_to_equal("#output", "initial")
14+
# pause for sync
15+
time.sleep(.2)
16+
dash_duo.find_element("#start").click()
17+
18+
dash_duo.wait_for_text_to_equal("#secondary", "first")
19+
dash_duo.wait_for_style_to_equal(
20+
"#secondary", "background-color", "rgba(255, 0, 0, 1)"
21+
)
22+
dash_duo.wait_for_text_to_equal("#output", "initial")
23+
dash_duo.wait_for_text_to_equal("#secondary", "second")
24+
dash_duo.wait_for_text_to_equal("#output", "completed")
25+
26+
dash_duo.find_element("#start-no-output").click()
27+
28+
dash_duo.wait_for_text_to_equal("#no-output", "started")
29+
dash_duo.wait_for_text_to_equal("#no-output", "completed")
30+
31+
32+
@pytest.mark.skipif(
33+
sys.version_info < (3, 7), reason="Python 3.6 long callbacks tests hangs up"
34+
)
35+
@flaky(max_runs=3)
36+
def test_002ab_basic(dash_duo, manager):
37+
"""
38+
Make sure that we settle to the correct final value when handling rapid inputs
39+
"""
40+
lock = Lock()
41+
with setup_background_callback_app(manager, "app1_async") as app:
42+
dash_duo.start_server(app)
43+
dash_duo.wait_for_text_to_equal("#output-1", "initial value", 15)
44+
input_ = dash_duo.find_element("#input")
45+
# pause for sync
46+
time.sleep(.2)
47+
dash_duo.clear_input(input_)
48+
49+
for key in "hello world":
50+
with lock:
51+
input_.send_keys(key)
52+
53+
dash_duo.wait_for_text_to_equal("#output-1", "hello world", 8)
54+
55+
assert not dash_duo.redux_state_is_loading
56+
assert dash_duo.get_logs() == []
57+

tests/integration/async/test_async_callbacks.py renamed to tests/integration/async_tests/test_async_callbacks.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from tests.utils import test_async
2929

3030

31-
def test_async_cbsc001_simple_callback(dash_duo):
31+
def test_async_cbsc001_simple_callback(dash_duo, *args):
3232
if not test_async():
3333
return
3434
lock = Lock()
@@ -68,7 +68,7 @@ async def update_output(value):
6868
assert dash_duo.get_logs() == []
6969

7070

71-
def test_async_cbsc002_callbacks_generating_children(dash_duo):
71+
def test_async_cbsc002_callbacks_generating_children(dash_duo, *args):
7272
"""Modify the DOM tree by adding new components in the callbacks."""
7373
if not test_async():
7474
return
@@ -154,7 +154,7 @@ async def update_input(value):
154154
assert dash_duo.get_logs() == [], "console is clean"
155155

156156

157-
def test_async_cbsc003_callback_with_unloaded_async_component(dash_duo):
157+
def test_async_cbsc003_callback_with_unloaded_async_component(dash_duo, *args):
158158
if not test_async():
159159
return
160160
app = Dash()
@@ -189,7 +189,7 @@ async def update_out(n_clicks):
189189
assert dash_duo.get_logs() == []
190190

191191

192-
def test_async_cbsc004_callback_using_unloaded_async_component(dash_duo):
192+
def test_async_cbsc004_callback_using_unloaded_async_component(dash_duo, *args):
193193
if not test_async():
194194
return
195195
app = Dash()
@@ -322,7 +322,7 @@ async def set_out(opts):
322322
strict=False,
323323
)
324324
@pytest.mark.parametrize("refresh", [False, True])
325-
def test_async_cbsc007_parallel_updates(refresh, dash_duo):
325+
def test_async_cbsc007_parallel_updates(refresh, dash_duo, *args):
326326
# This is a funny case, that seems to mostly happen with dcc.Location
327327
# but in principle could happen in other cases too:
328328
# A callback chain (in this case the initial hydration) is set to update a
@@ -377,7 +377,7 @@ async def set_path(n):
377377
dash_duo.wait_for_text_to_equal("#out", '[{"a": "/2:a"}] - /2')
378378

379379

380-
def test_async_cbsc008_wildcard_prop_callbacks(dash_duo):
380+
def test_async_cbsc008_wildcard_prop_callbacks(dash_duo, *args):
381381
if not test_async():
382382
return
383383
lock = Lock()
@@ -444,7 +444,7 @@ async def update_text(data):
444444
assert dash_duo.get_logs() == []
445445

446446

447-
def test_async_cbsc009_callback_using_unloaded_async_component_and_graph(dash_duo):
447+
def test_async_cbsc009_callback_using_unloaded_async_component_and_graph(dash_duo, *args):
448448
if not test_async():
449449
return
450450
app = Dash(__name__)
@@ -489,7 +489,7 @@ async def content(n, d, v):
489489
assert dash_duo.get_logs() == []
490490

491491

492-
def test_async_cbsc010_event_properties(dash_duo):
492+
def test_async_cbsc010_event_properties(dash_duo, *args):
493493
if not test_async():
494494
return
495495
app = Dash(__name__)
@@ -513,7 +513,7 @@ async def update_output(n_clicks):
513513
assert call_count.value == 1
514514

515515

516-
def test_async_cbsc011_one_call_for_multiple_outputs_initial(dash_duo):
516+
def test_async_cbsc011_one_call_for_multiple_outputs_initial(dash_duo, *args):
517517
if not test_async():
518518
return
519519
app = Dash(__name__)
@@ -551,7 +551,7 @@ async def dynamic_output(*args):
551551
assert dash_duo.get_logs() == []
552552

553553

554-
def test_async_cbsc012_one_call_for_multiple_outputs_update(dash_duo):
554+
def test_async_cbsc012_one_call_for_multiple_outputs_update(dash_duo, *args):
555555
if not test_async():
556556
return
557557
app = Dash(__name__, suppress_callback_exceptions=True)
@@ -603,7 +603,7 @@ async def dynamic_output(*args):
603603
assert dash_duo.get_logs() == []
604604

605605

606-
def test_async_cbsc013_multi_output_out_of_order(dash_duo):
606+
def test_async_cbsc013_multi_output_out_of_order(dash_duo, *args):
607607
if not test_async():
608608
return
609609
app = Dash(__name__)
@@ -704,7 +704,7 @@ async def update_output(n1, t1, n2, t2):
704704
assert call_count.value == 4
705705

706706

707-
def test_async_cbsc015_input_output_callback(dash_duo):
707+
def test_async_cbsc015_input_output_callback(dash_duo, *args):
708708
if not test_async():
709709
return
710710
return
@@ -757,7 +757,7 @@ def test_async_cbsc015_input_output_callback(dash_duo):
757757
# assert dash_duo.get_logs() == []
758758

759759

760-
def test_async_cbsc016_extra_components_callback(dash_duo):
760+
def test_async_cbsc016_extra_components_callback(dash_duo, *args):
761761
if not test_async():
762762
return
763763
lock = Lock()
@@ -818,7 +818,7 @@ def test_async_cbsc017_callback_directly_callable():
818818
# assert update_output("my-value") == "returning my-value"
819819

820820

821-
def test_async_cbsc018_callback_ndarray_output(dash_duo):
821+
def test_async_cbsc018_callback_ndarray_output(dash_duo, *args):
822822
if not test_async():
823823
return
824824
app = Dash(__name__)
@@ -836,7 +836,7 @@ async def on_click(_):
836836
assert dash_duo.get_logs() == []
837837

838838

839-
def test_async_cbsc019_callback_running(dash_duo):
839+
def test_async_cbsc019_callback_running(dash_duo, *args):
840840
if not test_async():
841841
return
842842
lock = Lock()
@@ -870,7 +870,7 @@ async def on_click(_):
870870
dash_duo.wait_for_text_to_equal("#running", "off")
871871

872872

873-
def test_async_cbsc020_callback_running_non_existing_component(dash_duo):
873+
def test_async_cbsc020_callback_running_non_existing_component(dash_duo, *args):
874874
if not test_async():
875875
return
876876
lock = Lock()
@@ -907,7 +907,7 @@ async def on_click(_):
907907
dash_duo.wait_for_text_to_equal("#output", "done")
908908

909909

910-
def test_async_cbsc021_callback_running_non_existing_component(dash_duo):
910+
def test_async_cbsc021_callback_running_non_existing_component(dash_duo, *args):
911911
if not test_async():
912912
return
913913
lock = Lock()

0 commit comments

Comments
 (0)