def test_plot_dict_returned_when_wrap_plot_info_is_false( self, mock_make_id: MagicMock) -> None: doc = Document() plot1 = figure() plot1.circle([], []) doc.add_root(plot1) plot2 = figure() plot2.circle([], []) doc.add_root(plot2) expected_plotdict_1 = RenderRoot(elementid=ID("ID"), id=ID("ID")) expected_plotdict_2 = RenderRoot(elementid=ID("ID"), id=ID("ID")) _, plotdict = bes.components(plot1, wrap_plot_info=False) assert plotdict == expected_plotdict_1 _, plotids = bes.components((plot1, plot2), wrap_plot_info=False) assert plotids == (expected_plotdict_1, expected_plotdict_2) _, plotiddict = bes.components({ 'p1': plot1, 'p2': plot2 }, wrap_plot_info=False) assert plotiddict == { 'p1': expected_plotdict_1, 'p2': expected_plotdict_2 }
def test_session_callbacks(self) -> None: d = Document() cm = bdc.DocumentCallbackManager(d) assert set(cm.session_callbacks) == set() s1 = SessionCallback(lambda: None, callback_id=ID("1")) cm._session_callbacks.add(s1) assert set(cm.session_callbacks) == {s1} s2 = SessionCallback(lambda: None, callback_id=ID("2")) cm._session_callbacks.add(s2) assert set(cm.session_callbacks) == {s1, s2}
def test_log_stats(ManagedServerLoop: MSL) -> None: application = Application() with ManagedServerLoop(application) as server: server._tornado._log_stats() session1 = pull_session(session_id=ID("session1"), url=url(server), io_loop=server.io_loop) session2 = pull_session(session_id=ID("session2"), url=url(server), io_loop=server.io_loop) server._tornado._log_stats() session1.close() session2.close() server._tornado._log_stats()
def test_push_document(self, ManagedServerLoop: MSL) -> None: application = Application() with ManagedServerLoop(application) as server: doc = document.Document() doc.add_root(AnotherModelInTestClientServer(bar=43)) doc.add_root(SomeModelInTestClientServer(foo=42)) client_session = push_session(doc, session_id=ID("test_push_document"), url=url(server), io_loop=server.io_loop) assert client_session.document == doc assert len(client_session.document.roots) == 2 server_session = server.get_session('/', client_session.id) assert len(server_session.document.roots) == 2 results = {} for r in server_session.document.roots: if hasattr(r, 'foo'): results['foo'] = r.foo if hasattr(r, 'bar'): results['bar'] = r.bar assert results['foo'] == 42 assert results['bar'] == 43 client_session.close() client_session._loop_until_closed() assert not client_session.connected
async def test_multiple_validation_success_with_multiple_buffers() -> None: r = receiver.Receiver(proto) for N in range(10): partial = await r.consume( f'{{"msgtype": "PATCH-DOC", "msgid": "10", "num_buffers":{N}}}') partial = await r.consume('{}') partial = await r.consume('{"bar": 10}') for i in range(N): partial = await r.consume(f'{{"id": "header{i}"}}') partial = await r.consume(f'payload{i}'.encode()) assert partial is not None assert partial.msgtype == "PATCH-DOC" assert partial.header == { "msgtype": "PATCH-DOC", "msgid": "10", "num_buffers": N } assert partial.content == {"bar": 10} assert partial.metadata == {} for i in range(N): assert partial.buffers[i] == Buffer(ID(f"header{i}"), f"payload{i}".encode())
def test_client_session_periodic_async_added_before_push( self, ManagedServerLoop: MSL) -> None: application = Application() with ManagedServerLoop(application) as server: doc = document.Document() result = DictModel() doc.add_root(result) async def cb(): result.values['a'] = 0 result.values['b'] = await self.async_value(1) result.values['c'] = await self.async_value(2) result.values['d'] = await self.async_value(3) result.values['e'] = await self.async_value(4) client_session.close() return 5 cb_id = doc.add_periodic_callback(cb, 10) client_session = push_session( doc, session_id=ID("test_client_session_periodic_async"), url=url(server), io_loop=server.io_loop) client_session._loop_until_closed() doc.remove_periodic_callback(cb_id) assert dict(a=0, b=1, c=2, d=3, e=4) == result.values
def test_remove_session_callback(self) -> None: d = Document() cm = bdc.DocumentCallbackManager(d) events = [] def listener(event: DocumentChangedEvent) -> None: events.append(event) cm.on_change(listener) assert len(cm.session_callbacks) == 0 assert not events def cb() -> None: pass obj = SessionCallback(cb, callback_id=ID("1")) cm.add_session_callback(obj, cb, one_shot=False) cm.remove_session_callback(obj) assert len(cm.session_callbacks) == 0 assert len(events) == 2 assert isinstance(events[0], SessionCallbackAdded) assert isinstance(events[1], SessionCallbackRemoved)
async def test_validation_success_with_one_buffer() -> None: r = receiver.Receiver(proto) partial = await r.consume( '{"msgtype": "PATCH-DOC", "msgid": "10", "num_buffers":1}') assert partial is None partial = await r.consume('{}') assert partial is None partial = await r.consume('{"bar": 10}') assert partial is None partial = await r.consume('{"id": "buf_header"}') assert partial is None partial = await r.consume(b'payload') assert partial is not None assert partial.msgtype == "PATCH-DOC" assert partial.header == { "msgtype": "PATCH-DOC", "msgid": "10", "num_buffers": 1 } assert partial.content == {"bar": 10} assert partial.metadata == {} assert partial.buffers == [Buffer(ID("buf_header"), b"payload")]
def test_server_session_periodic_async(self, ManagedServerLoop: MSL) -> None: application = Application() with ManagedServerLoop(application) as server: doc = document.Document() doc.add_root(DictModel()) client_session = push_session( doc, session_id=ID("test_server_session_periodic_async"), url=url(server), io_loop=server.io_loop) server_session = server.get_session('/', client_session.id) result = next(iter(server_session.document.roots)) async def cb(): # we're testing that we can modify the doc and be # "inside" the document lock result.values['a'] = 0 result.values['b'] = await self.async_value(1) result.values['c'] = await self.async_value(2) result.values['d'] = await self.async_value(3) result.values['e'] = await self.async_value(4) client_session.close() return 5 cb_id = server_session.document.add_periodic_callback(cb, 10) client_session._loop_until_closed() server_session.document.remove_periodic_callback(cb_id) assert dict(a=0, b=1, c=2, d=3, e=4) == result.values
def test_ping(self, ManagedServerLoop: MSL) -> None: application = Application() with ManagedServerLoop(application, keep_alive_milliseconds=0) as server: session = ClientSession(session_id=ID("test_ping"), websocket_url=ws_url(server), io_loop=server.io_loop) session.connect() assert session.connected assert session.document is None connection = next(iter(server._tornado._clients)) expected_pong = connection._ping_count server._tornado._keep_alive() # send ping session.force_roundtrip() assert expected_pong == connection._socket.latest_pong # check that each ping increments by 1 server._tornado._keep_alive() session.force_roundtrip() assert (expected_pong + 1) == connection._socket.latest_pong session.close() session._loop_until_closed() assert not session.connected
def test_client_session_next_tick_async(self, ManagedServerLoop: MSL) -> None: application = Application() with ManagedServerLoop(application) as server: doc = document.Document() client_session = push_session( doc, session_id=ID("test_client_session_next_tick_async"), url=url(server), io_loop=server.io_loop) result = DictModel() doc.add_root(result) async def cb(): result.values['a'] = 0 result.values['b'] = await self.async_value(1) result.values['c'] = await self.async_value(2) result.values['d'] = await self.async_value(3) result.values['e'] = await self.async_value(4) client_session.close() return 5 cb_id = doc.add_next_tick_callback(cb) client_session._loop_until_closed() with pytest.raises(ValueError) as exc: doc.remove_next_tick_callback(cb_id) assert 'already removed' in repr(exc.value) assert dict(a=0, b=1, c=2, d=3, e=4) == result.values
def test_server_examples(server_example: Example, example: Example, report: List[Example], config: _pytest.config.Config, bokeh_server: str) -> None: if config.option.verbose: print() app = build_single_handler_application(example.path) doc = app.create_document() # remove all next-tick, periodic, and timeout callbacks for session_callback in doc.session_callbacks: if isinstance(session_callback, NextTickCallback): doc.remove_next_tick_callback(session_callback) elif isinstance(session_callback, PeriodicCallback): doc.remove_periodic_callback(session_callback) elif isinstance(session_callback, TimeoutCallback): doc.remove_timeout_callback(session_callback) else: raise RuntimeError('Unhandled callback type', type(session_callback)) session_id = ID(basename(example.path)) push_session(doc, session_id=session_id) if example.no_js: if not config.option.no_js: warn(f"skipping bokehjs for {example.relpath}") else: _run_in_browser( example, f"http://localhost:5006/?bokeh-session-id={session_id}", report, config.option.verbose)
def test__check_error_404(self, ManagedServerLoop: MSL) -> None: application = Application() with ManagedServerLoop(application) as server: with pytest.raises(IOError): pull_session(session_id=ID("test__check_error_404"), url=url(server) + 'file_not_found', io_loop=server.io_loop)
def test_pull_large_document(self, ManagedServerLoop: MSL) -> None: application = Application() def add_roots(doc): import numpy as np rows, cols = (40000, 100) columns = ['x' + str(i) for i in range(cols)] a = np.random.randn(cols, rows) source = ColumnDataSource(data=dict(zip(columns, a))) doc.add_root(source) handler = FunctionHandler(add_roots) application.add(handler) with ManagedServerLoop(application) as server: client_session = pull_session(session_id=ID("test_pull_document"), url=url(server), io_loop=server.io_loop, max_message_size=50000000) assert len(client_session.document.roots) == 1 server_session = server.get_session('/', client_session.id) assert len(server_session.document.roots) == 1 results = {} for r in server_session.document.roots: if hasattr(r, 'data'): results['data'] = r.data assert len(list(results['data'].keys())) == 100 assert all(len(x) == 40000 for x in results['data'].values()) client_session.close() client_session._loop_until_closed() assert not client_session.connected
def test_pull_document(self, ManagedServerLoop: MSL) -> None: application = Application() def add_roots(doc: Document): doc.add_root(AnotherModelInTestClientServer(bar=43)) doc.add_root( SomeModelInTestClientServer(foo=42, data=bytes( [0x00, 0x01, 0xFE, 0xFF]))) handler = FunctionHandler(add_roots) application.add(handler) with ManagedServerLoop(application) as server: client_session = pull_session(session_id=ID("test_pull_document"), url=url(server), io_loop=server.io_loop) assert len(client_session.document.roots) == 2 server_session = server.get_session('/', client_session.id) assert len(server_session.document.roots) == 2 results = {} for r in server_session.document.roots: if hasattr(r, 'foo'): results['foo'] = r.foo if hasattr(r, 'bar'): results['bar'] = r.bar assert results['foo'] == 42 assert results['bar'] == 43 client_session.close() client_session._loop_until_closed() assert not client_session.connected
def test_server_changes_do_not_boomerang(monkeypatch: pytest.MonkeyPatch, ManagedServerLoop: MSL) -> None: application = Application() with ManagedServerLoop(application) as server: doc = document.Document() client_root = SomeModelInTestClientServer( foo=42, data=bytes([0x00, 0x01, 0xFE, 0xFF])) doc.add_root(client_root) client_session = push_session( doc, session_id=ID("test_server_changes_do_not_boomerang"), url=url(server), io_loop=server.io_loop) server_session = server.get_session('/', client_session.id) assert len(server_session.document.roots) == 1 server_root = next(iter(server_session.document.roots)) assert client_root.foo == 42 assert server_root.foo == 42 got_angry = {} got_angry['result'] = None # trap any boomerang def get_angry(message, connection): got_angry['result'] = message monkeypatch.setattr(server_session, '_handle_patch', get_angry) # Now modify the server document def do_set_foo_property(): server_root.foo = 57 server.io_loop.add_callback(server_session.with_document_locked, do_set_foo_property) # there is no great way to block until the server # has applied changes, since patches are sent # asynchronously. We use internal _loop_until API. def client_change_made(): return client_root.foo == 57 client_session._connection._loop_until(client_change_made) assert client_root.foo == 57 # force a round trip to be sure we get the boomerang if we're going to client_session.force_roundtrip() assert got_angry['result'] is None client_session.close() client_session._loop_until_closed() assert not client_session.connected
def test_client_changes_go_to_server(self, ManagedServerLoop: MSL) -> None: application = Application() with ManagedServerLoop(application) as server: doc = document.Document() client_root = SomeModelInTestClientServer( foo=42, data=bytes([0x00, 0x01, 0xFE, 0xFF])) client_session = push_session( doc, session_id=ID("test_client_changes_go_to_server"), url=url(server), io_loop=server.io_loop) server_session = server.get_session('/', client_session.id) assert len(server_session.document.roots) == 0 doc.add_root(client_root) client_session.force_roundtrip( ) # be sure events have been handled on server assert len(server_session.document.roots) == 1 server_root = next(iter(server_session.document.roots)) assert client_root.foo == 42 assert server_root.foo == 42 # Now try setting title assert server_session.document.title == document.DEFAULT_TITLE doc.title = "Client Title" client_session.force_roundtrip( ) # be sure events have been handled on server assert server_session.document.title == "Client Title" # Now modify an attribute on a client model client_root.foo = 57 # there is no great way to block until the server # has applied changes, since patches are sent # asynchronously. We use internal _loop_until API. def server_change_made(): return server_root.foo == 57 client_session._connection._loop_until(server_change_made) assert server_root.foo == 57 doc.remove_root(client_root) client_session.force_roundtrip( ) # be sure events have been handled on server assert len(server_session.document.roots) == 0 client_session.close() client_session._loop_until_closed() assert not client_session.connected
def test_version(self, monkeypatch: pytest.MonkeyPatch, test_plot: figure) -> None: from bokeh import __version__ out = bes.json_item(test_plot, target=ID("foo")) assert set(out.keys()) == JSON_ITEMS_KEYS assert out['doc']['version'] == __version__ out = bes.json_item(test_plot) assert set(out.keys()) == JSON_ITEMS_KEYS assert out['doc']['version'] == __version__
def test_minimal_connect_and_disconnect(self, ManagedServerLoop: MSL) -> None: application = Application() with ManagedServerLoop(application) as server: # we don't have to start the server because it # uses the same main loop as the client, so # if we start either one it starts both session = ClientSession( session_id=ID("test_minimal_connect_and_disconnect"), io_loop=server.io_loop, websocket_url=ws_url(server)) session.connect() assert session.connected
def test_client_changes_do_not_boomerang(monkeypatch: pytest.MonkeyPatch, ManagedServerLoop: MSL) -> None: application = Application() with ManagedServerLoop(application) as server: doc = document.Document() client_root = SomeModelInTestClientServer( foo=42, data=bytes([0x00, 0x01, 0xFE, 0xFF])) doc.add_root(client_root) client_session = push_session( doc, session_id=ID("test_client_changes_do_not_boomerang"), url=url(server), io_loop=server.io_loop) server_session = server.get_session('/', client_session.id) assert len(server_session.document.roots) == 1 server_root = next(iter(server_session.document.roots)) assert client_root.foo == 42 assert server_root.foo == 42 got_angry = {} got_angry['result'] = None # trap any boomerang def get_angry(message): got_angry['result'] = message monkeypatch.setattr(client_session, '_handle_patch', get_angry) # Now modify the client document client_root.foo = 57 # wait until server side change made ... but we may not have the # boomerang yet def server_change_made(): return server_root.foo == 57 client_session._connection._loop_until(server_change_made) assert server_root.foo == 57 # force a round trip to be sure we get the boomerang if we're going to client_session.force_roundtrip() assert got_angry['result'] is None client_session.close() client_session._loop_until_closed() assert not client_session.connected server.unlisten() # clean up so next test can run
def test__lifecycle_hooks(ManagedServerLoop: MSL) -> None: application = Application() handler = HookTestHandler() application.add(handler) with ManagedServerLoop(application, check_unused_sessions_milliseconds=30) as server: client_session = pull_session(session_id=ID("test__lifecycle_hooks"), url=url(server), io_loop=server.io_loop) client_doc = client_session.document assert len(client_doc.roots) == 1 server_session = server.get_session('/', client_session.id) server_doc = server_session.document assert len(server_doc.roots) == 1 # save for later, since doc.roots will be emptied after the session is closed client_hook_list = list(client_doc.roots[0].hooks) server_hook_list = list(server_doc.roots[0].hooks) client_session.close() # expire the session quickly rather than after the usual timeout server_session.request_expiration() server.io_loop.call_later(0.1, lambda: server.io_loop.stop()) server.io_loop.start() assert handler.hooks == [ "server_loaded", "session_created", "modify", "next_tick", "timeout", "periodic", "session_destroyed", "server_unloaded", ] assert handler.load_count == 1 assert handler.unload_count == 1 # 3 instead of 6, because locked callbacks on destroyed sessions become no-ops assert handler.session_creation_async_value == 3 assert client_doc.title == "Modified" assert server_doc.title == "Modified" # only the handler sees "session_destroyed" since the session is shut down at that point. assert client_hook_list == ["session_created", "modify"] assert server_hook_list == ["session_created", "modify"]
def test_disconnect_on_error(self, ManagedServerLoop: MSL) -> None: application = Application() with ManagedServerLoop(application) as server: session = ClientSession(session_id=ID("test_disconnect_on_error"), websocket_url=ws_url(server), io_loop=server.io_loop) session.connect() assert session.connected # send a bogus message using private fields server.io_loop.add_callback( session._connection._socket.write_message, b"xx", binary=True) # connection should now close on the server side # and the client loop should end session._loop_until_closed() assert not session.connected session.close() session._loop_until_closed() assert not session.connected
def test_request_server_info(self, ManagedServerLoop: MSL) -> None: application = Application() with ManagedServerLoop(application) as server: session = ClientSession(session_id=ID("test_request_server_info"), websocket_url=ws_url(server), io_loop=server.io_loop) session.connect() assert session.connected assert session.document is None info = session.request_server_info() from bokeh import __version__ assert info['version_info']['bokeh'] == __version__ assert info['version_info']['server'] == __version__ session.close() session._loop_until_closed() assert not session.connected
def stable_id() -> ID: return ID('ID')
def test_root_id(self, test_plot: figure) -> None: out = bes.json_item(test_plot, target=ID("foo")) assert set(out.keys()) == JSON_ITEMS_KEYS assert out['doc']['roots'][0]["id"] == out['root_id']
def test_doc_title(self, test_plot: figure) -> None: out = bes.json_item(test_plot, target=ID("foo")) assert set(out.keys()) == JSON_ITEMS_KEYS assert out['doc']['title'] == ""
def test_doc_json(self, test_plot: figure) -> None: out = bes.json_item(test_plot, target=ID("foo")) assert set(out.keys()) == JSON_ITEMS_KEYS expected = list(standalone_docs_json([test_plot]).values())[0] assert out['doc'] == expected
def test_with_target_id(self, test_plot: figure) -> None: out = bes.json_item(test_plot, target=ID("foo")) assert set(out.keys()) == JSON_ITEMS_KEYS assert out['target_id'] == "foo"
def test_server_changes_go_to_client(self, ManagedServerLoop: MSL) -> None: application = Application() with ManagedServerLoop(application) as server: doc = document.Document() client_session = push_session( doc, session_id=ID("test_server_changes_go_to_client"), url=url(server), io_loop=server.io_loop) server_session = server.get_session('/', client_session.id) assert len(client_session.document.roots) == 0 server_root = SomeModelInTestClientServer( foo=42, data=bytes([0x00, 0x01, 0xFE, 0xFF])) def do_add_server_root(): server_session.document.add_root(server_root) server.io_loop.add_callback(server_session.with_document_locked, do_add_server_root) def client_has_root(): return len(doc.roots) > 0 client_session._connection._loop_until(client_has_root) client_root = next(iter(client_session.document.roots)) assert client_root.foo == 42 assert server_root.foo == 42 # Now try setting title on server side def do_set_server_title(): server_session.document.title = "Server Title" server.io_loop.add_callback(server_session.with_document_locked, do_set_server_title) def client_title_set(): return client_session.document.title != document.DEFAULT_TITLE client_session._connection._loop_until(client_title_set) assert client_session.document.title == "Server Title" # Now modify a model within the server document def do_set_property_on_server(): server_root.foo = 57 server.io_loop.add_callback(server_session.with_document_locked, do_set_property_on_server) # there is no great way to block until the server # has applied changes, since patches are sent # asynchronously. We use internal _loop_until API. def client_change_made(): return client_root.foo == 57 client_session._connection._loop_until(client_change_made) assert client_root.foo == 57 def do_remove_server_root(): server_session.document.remove_root(server_root) server.io_loop.add_callback(server_session.with_document_locked, do_remove_server_root) def client_lacks_root(): return len(doc.roots) == 0 client_session._connection._loop_until(client_lacks_root) assert len(client_session.document.roots) == 0 client_session.close() client_session._loop_until_closed() assert not client_session.connected
def test_lots_of_concurrent_messages(self, ManagedServerLoop: MSL) -> None: application = Application() def setup_stuff(doc): m1 = AnotherModelInTestClientServer(bar=43, name='m1') m2 = SomeModelInTestClientServer(foo=42, name='m2', data=bytes( [0x00, 0x01, 0xFE, 0xFF])) m3 = SomeModelInTestClientServer(foo=68, name='m3', data=bytes( [0x00, 0x01, 0xFE, 0xFF])) doc.add_root(m1) doc.add_root(m2) doc.add_root(m3) def timeout1(): m1.bar += 1 timeout1_cb_id = doc.add_timeout_callback(timeout1, 1) def timeout2(): m2.foo += 1 timeout2_cb_id = doc.add_timeout_callback(timeout2, 3) def periodic1(): m1.bar += 1 doc.remove_timeout_callback(timeout1_cb_id) doc.add_timeout_callback(timeout1, m1.bar % 7) doc.add_periodic_callback(periodic1, 3) def periodic2(): m2.foo += 1 doc.remove_timeout_callback(timeout2_cb_id) doc.add_timeout_callback(timeout2, m2.foo % 7) doc.add_periodic_callback(periodic2, 1) def server_on_change(event): if isinstance(event, ModelChangedEvent) and event.model is m3: return m3.foo += 1 doc.on_change(server_on_change) handler = FunctionHandler(setup_stuff) application.add(handler) # keep_alive_milliseconds=1 sends pings as fast as the OS will let us with ManagedServerLoop(application, keep_alive_milliseconds=1) as server: session = pull_session( session_id=ID("test_lots_of_concurrent_messages"), url=url(server), io_loop=server.io_loop) assert session.connected server_session = server.get_session('/', session.id) def client_timeout(): m = session.document.roots[0] m.name = m.name[::-1] cb_id = session.document.add_timeout_callback(client_timeout, 3) def client_periodic(): m = session.document.roots[1] m.name = m.name[::-1] session.document.remove_timeout_callback(cb_id) session.document.add_timeout_callback(client_timeout, 3) session.document.add_periodic_callback(client_periodic, 1) result = {} def end_test(): result['connected'] = session.connected result[ 'server_connection_count'] = server_session.connection_count result['server_close_code'] = next( iter(server._tornado._clients))._socket.close_code result['doc'] = session.document.to_json() session.close() # making this longer is more likely to trigger bugs, but it also # makes the test take however much time you put here session.document.add_timeout_callback(end_test, 250) def client_on_change(event): if not isinstance(event, TitleChangedEvent): session.document.title = session.document.title[::-1] session.document.on_change(client_on_change) session._loop_until_closed() assert not session.connected # we should have still been connected at the end, # if we didn't have any crazy protocol errors assert 'connected' in result assert result['connected'] # server should also still have been connected assert result['server_connection_count'] == 1 assert result['server_close_code'] is None