class ScriptCheckEndpointDoesNotExistTest(tornado.testing.AsyncHTTPTestCase): async def does_script_run_without_error(self): self.fail("Should not be called") def setUp(self): self._server = Server(None, None, "test command line") self._server.does_script_run_without_error = self.does_script_run_without_error self._old_config = config.get_option("server.scriptHealthCheckEnabled") config._set_option("server.scriptHealthCheckEnabled", False, "test") super().setUp() def tearDown(self): config._set_option("server.scriptHealthCheckEnabled", self._old_config, "test") Server._singleton = None super().tearDown() def get_app(self): return self._server._create_app() def test_endpoint(self): response = self.fetch("/script-health-check") self.assertEqual(404, response.code)
class ServerTestCase(tornado.testing.AsyncHTTPTestCase): """Base class for async streamlit.server testing. Subclasses should patch 'streamlit.server.server.AppSession', to prevent AppSessions from being created, and scripts from being run. (Script running involves creating new threads, which interfere with other tests if not properly terminated.) See the "ServerTest" class for example usage. """ _next_session_id = 0 def get_app(self): # Create a Server, and patch its _on_stopped function # to no-op. This prevents it from shutting down the # ioloop when it stops. self.server = Server(self.io_loop, "/not/a/script.py", "test command line") self.server._on_stopped = mock.MagicMock() # type: ignore[assignment] app = self.server._create_app() return app def tearDown(self): super(ServerTestCase, self).tearDown() # Clear the Server singleton for the next test Server._singleton = None def start_server_loop(self): """Starts the server's loop coroutine. Returns ------- Future A Future that resolves when the loop has started. You need to yield on this value from within a 'tornado.testing.gen_test' coroutine. """ server_started = Future() # type: ignore[var-annotated] self.io_loop.spawn_callback(self.server._loop_coroutine, lambda _: server_started.set_result(None)) return server_started def get_ws_url(self, path): """Return a ws:// URL with the given path for our test server.""" # get_url() gives us a result with the 'http' scheme; # we swap it out for 'ws'. url = self.get_url(path) parts = list(urllib.parse.urlparse(url)) parts[0] = "ws" return urllib.parse.urlunparse(tuple(parts)) def ws_connect(self): """Open a websocket connection to the server. Returns ------- Future A Future that resolves with the connected websocket client. You need to yield on this value from within a 'tornado.testing.gen_test' coroutine. """ return tornado.websocket.websocket_connect(self.get_ws_url("/stream")) @tornado.gen.coroutine def read_forward_msg(self, ws_client): """Parse the next message from a Websocket client into a ForwardMsg.""" data = yield ws_client.read_message() message = ForwardMsg() message.ParseFromString(data) raise gen.Return(message) @staticmethod def _create_mock_app_session(*args, **kwargs): """Create a mock AppSession. Each mocked instance will have its own unique ID.""" mock_id = mock.PropertyMock(return_value="mock_id:%s" % ServerTestCase._next_session_id) ServerTestCase._next_session_id += 1 mock_session = mock.MagicMock(AppSession, autospec=True, *args, **kwargs) type(mock_session).id = mock_id return mock_session def _patch_app_session(self): """Mock the Server's AppSession import. We don't want actual sessions to be instantiated, or scripts to be run. """ return mock.patch( "streamlit.server.server.AppSession", # new_callable must return a function, not an object, or else # there will only be a single AppSession mock. Hence the lambda. new_callable=lambda: self._create_mock_app_session, )
class ServerTestCase(tornado.testing.AsyncHTTPTestCase): """Base class for async streamlit.server testing. Subclasses should patch 'streamlit.server.server.ReportSession', to prevent ReportSessions from being created, and scripts from being run. (Script running involves creating new threads, which interfere with other tests if not properly terminated.) See the "ServerTest" class for example usage. """ def get_app(self): # Create a Server, and patch its _on_stopped function # to no-op. This prevents it from shutting down the # ioloop when it stops. self.server = Server(self.io_loop, "/not/a/script.py", "test command line") self.server._on_stopped = mock.MagicMock() # type: ignore[assignment] app = self.server._create_app() return app def tearDown(self): super(ServerTestCase, self).tearDown() # Clear the Server singleton for the next test Server._singleton = None def start_server_loop(self): """Starts the server's loop coroutine. Returns ------- Future A Future that resolves when the loop has started. You need to yield on this value from within a 'tornado.testing.gen_test' coroutine. """ server_started = Future() # type: ignore[var-annotated] self.io_loop.spawn_callback(self.server._loop_coroutine, lambda _: server_started.set_result(None)) return server_started def get_ws_url(self, path): """Return a ws:// URL with the given path for our test server.""" # get_url() gives us a result with the 'http' scheme; # we swap it out for 'ws'. url = self.get_url(path) parts = list(urllib.parse.urlparse(url)) parts[0] = "ws" return urllib.parse.urlunparse(tuple(parts)) def ws_connect(self): """Open a websocket connection to the server. Returns ------- Future A Future that resolves with the connected websocket client. You need to yield on this value from within a 'tornado.testing.gen_test' coroutine. """ return tornado.websocket.websocket_connect(self.get_ws_url("/stream")) @tornado.gen.coroutine def read_forward_msg(self, ws_client): """Parse the next message from a Websocket client into a ForwardMsg.""" data = yield ws_client.read_message() message = ForwardMsg() message.ParseFromString(data) raise gen.Return(message)