def test_rerun_caching(self): """Test that st.caches are maintained across script runs.""" # Make sure there are no caches from other tests. caching._mem_caches.clear() # Run st_cache_script. runner = TestScriptRunner("st_cache_script.py") runner.request_rerun(RerunData()) runner.start() runner.join() # The script has 5 cached functions, each of which writes out # some text. self._assert_text_deltas( runner, [ "cached function called", "cached function called", "cached function called", "cached function called", "cached_depending_on_not_yet_defined called", ], ) # Re-run the script on a second runner. runner = TestScriptRunner("st_cache_script.py") runner.request_rerun(RerunData()) runner.start() runner.join() # The cached functions should not have been called on this second run self._assert_text_deltas(runner, [])
def test_calls_widget_callbacks_on_new_scriptrunner_instance( self, patched_call_callbacks): """A new ScriptRunner instance will call widget callbacks if widget values have changed. (This differs slightly from `test_calls_widget_callbacks`, which tests that an *already-running* ScriptRunner calls its callbacks on rerun). """ # Create a ScriptRunner and run it once so we can grab its widgets. scriptrunner = TestScriptRunner("widgets_script.py") scriptrunner.request_rerun(RerunData()) scriptrunner.start() require_widgets_deltas([scriptrunner]) scriptrunner.request_stop() scriptrunner.join() patched_call_callbacks.assert_not_called() # Set our checkbox's value to True states = WidgetStates() checkbox_id = scriptrunner.get_widget_id("checkbox", "checkbox") _create_widget(checkbox_id, states).bool_value = True # Create a *new* ScriptRunner with our new RerunData. Our callbacks # should be called this time. scriptrunner = TestScriptRunner("widgets_script.py") scriptrunner.request_rerun(RerunData(widget_states=states)) scriptrunner.start() require_widgets_deltas([scriptrunner]) scriptrunner.request_stop() scriptrunner.join() patched_call_callbacks.assert_called_once()
def test_rerun_coalesce_widgets_and_widgets(self): """Coalesce two non-null-WidgetStates rerun requests.""" reqs = ScriptRequests() # Request a rerun with non-null WidgetStates. states = WidgetStates() _create_widget("trigger", states).trigger_value = True _create_widget("int", states).int_value = 123 success = reqs.request_rerun(RerunData(widget_states=states)) self.assertTrue(success) # Request another rerun. It should get coalesced with the first one. states = WidgetStates() _create_widget("trigger", states).trigger_value = False _create_widget("int", states).int_value = 456 success = reqs.request_rerun(RerunData(widget_states=states)) self.assertTrue(success) self.assertEqual(ScriptRequestType.RERUN, reqs._state) result_states = reqs._rerun_data.widget_states # Coalesced triggers should be True if either the old *or* # new value was True self.assertEqual(True, _get_widget("trigger", result_states).trigger_value) # Other widgets should have their newest value self.assertEqual(456, _get_widget("int", result_states).int_value)
def test_create_scriptrunner(self, mock_scriptrunner: MagicMock): """Test that _create_scriptrunner does what it should.""" session = _create_test_session() self.assertIsNone(session._scriptrunner) session._create_scriptrunner(initial_rerun_data=RerunData()) # Assert that the ScriptRunner constructor was called. mock_scriptrunner.assert_called_once_with( session_id=session.id, session_data=session._session_data, client_state=session._client_state, session_state=session._session_state, uploaded_file_mgr=session._uploaded_file_mgr, initial_rerun_data=RerunData(), ) self.assertIsNotNone(session._scriptrunner) # And that the ScriptRunner was initialized and started. scriptrunner: MagicMock = cast(MagicMock, session._scriptrunner) scriptrunner.on_event.connect.assert_called_once_with( session._on_scriptrunner_event ) scriptrunner.start.assert_called_once()
def test_run_script_in_loop(self): """_run_script_thread should continue re-running its script while it has pending rerun requests.""" scriptrunner = TestScriptRunner("not_a_script.py") # ScriptRequests.on_scriptrunner_ready will return 3 rerun requests, # and then stop. on_scriptrunner_ready_mock = MagicMock() on_scriptrunner_ready_mock.side_effect = [ ScriptRequest(ScriptRequestType.RERUN, RerunData()), ScriptRequest(ScriptRequestType.RERUN, RerunData()), ScriptRequest(ScriptRequestType.RERUN, RerunData()), ScriptRequest(ScriptRequestType.STOP), ] scriptrunner._requests.on_scriptrunner_ready = on_scriptrunner_ready_mock run_script_mock = MagicMock() scriptrunner._run_script = run_script_mock scriptrunner.start() scriptrunner.join() # _run_script should have been called 3 times, once for each # RERUN request. self._assert_no_exceptions(scriptrunner) self.assertEqual(3, run_script_mock.call_count)
def request_rerun(self, client_state: Optional[ClientState]) -> None: """Signal that we're interested in running the script. If the script is not already running, it will be started immediately. Otherwise, a rerun will be requested. Parameters ---------- client_state : streamlit.proto.ClientState_pb2.ClientState | None The ClientState protobuf to run the script with, or None to use previous client state. """ if self._state == AppSessionState.SHUTDOWN_REQUESTED: LOGGER.warning("Discarding rerun request after shutdown") return if client_state: rerun_data = RerunData( client_state.query_string, client_state.widget_states ) else: rerun_data = RerunData() if self._scriptrunner is None or not self._scriptrunner.request_rerun( rerun_data ): # If we are here, then either we have no ScriptRunner, or our # current ScriptRunner is shutting down and cannot handle a rerun # request - so we'll create and start a new ScriptRunner. self._create_scriptrunner(rerun_data)
def test_calls_widget_callbacks_error(self, patched_call_callbacks, patched_st_exception): """If an exception is raised from a callback function, it should result in a call to `streamlit.exception`. """ patched_call_callbacks.side_effect = RuntimeError("Random Error") scriptrunner = TestScriptRunner("widgets_script.py") scriptrunner.request_rerun(RerunData()) scriptrunner.start() # Default widget values require_widgets_deltas([scriptrunner]) self._assert_text_deltas( scriptrunner, ["False", "ahoy!", "0", "False", "loop_forever"]) patched_call_callbacks.assert_not_called() # Update widgets states = WidgetStates() w1_id = scriptrunner.get_widget_id("checkbox", "checkbox") _create_widget(w1_id, states).bool_value = True w2_id = scriptrunner.get_widget_id("text_area", "text_area") _create_widget(w2_id, states).string_value = "matey!" w3_id = scriptrunner.get_widget_id("radio", "radio") _create_widget(w3_id, states).int_value = 2 w4_id = scriptrunner.get_widget_id("button", "button") _create_widget(w4_id, states).trigger_value = True # Explicitly clear deltas before re-running, to prevent a race # condition. (The ScriptRunner will clear the deltas when it # starts the re-run, but if that doesn't happen before # require_widgets_deltas() starts polling the ScriptRunner's deltas, # it will see stale deltas from the last run.) scriptrunner.clear_forward_msgs() scriptrunner.request_rerun(RerunData(widget_states=states)) scriptrunner.join() patched_call_callbacks.assert_called_once() self._assert_control_events( scriptrunner, [ ScriptRunnerEvent.SCRIPT_STARTED, ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS, ScriptRunnerEvent.SCRIPT_STARTED, # We use the SCRIPT_STOPPED_WITH_SUCCESS event even if the # script runs into an error during execution. The user is # informed of the error by an `st.exception` box that we check # for below. ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS, ScriptRunnerEvent.SHUTDOWN, ], ) patched_st_exception.assert_called_once()
def test_on_script_complete_with_pending_request(self): """Return RERUN; transition to the CONTINUE state.""" reqs = ScriptRequests() reqs.request_rerun(RerunData()) result = reqs.on_scriptrunner_ready() self.assertEqual(ScriptRequest(ScriptRequestType.RERUN, RerunData()), result) self.assertEqual(ScriptRequestType.CONTINUE, reqs._state)
def off_test_multiple_scriptrunners(self): """Tests that multiple scriptrunners can run simultaneously.""" # This scriptrunner will run before the other 3. It's used to retrieve # the widget id before initializing deltas on other runners. scriptrunner = TestScriptRunner("widgets_script.py") scriptrunner.request_rerun(RerunData()) scriptrunner.start() # Get the widget ID of a radio button and shut down the first runner. require_widgets_deltas([scriptrunner]) radio_widget_id = scriptrunner.get_widget_id("radio", "radio") scriptrunner.request_stop() scriptrunner.join() self._assert_no_exceptions(scriptrunner) # Build several runners. Each will set a different int value for # its radio button. runners = [] for ii in range(3): runner = TestScriptRunner("widgets_script.py") runners.append(runner) states = WidgetStates() _create_widget(radio_widget_id, states).int_value = ii runner.request_rerun(RerunData(widget_states=states)) # Start the runners and wait a beat. for runner in runners: runner.start() require_widgets_deltas(runners) # Ensure that each runner's radio value is as expected. for ii, runner in enumerate(runners): self._assert_text_deltas( runner, ["False", "ahoy!", "%s" % ii, "False", "loop_forever"]) runner.request_stop() time.sleep(0.1) # Shut 'em all down! for runner in runners: runner.join() for runner in runners: self._assert_no_exceptions(runner) self._assert_control_events( runner, [ ScriptRunnerEvent.SCRIPT_STARTED, ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS, ScriptRunnerEvent.SHUTDOWN, ], )
def test_runtime_error(self, show_error_details: bool): """Tests that we correctly handle scripts with runtime errors.""" with testutil.patch_config_options( {"client.showErrorDetails": show_error_details}): scriptrunner = TestScriptRunner("runtime_error.py") scriptrunner.request_rerun(RerunData()) scriptrunner.start() scriptrunner.join() self._assert_no_exceptions(scriptrunner) self._assert_events( scriptrunner, [ ScriptRunnerEvent.SCRIPT_STARTED, ScriptRunnerEvent.ENQUEUE_FORWARD_MSG, # text delta ScriptRunnerEvent.ENQUEUE_FORWARD_MSG, # exception delta ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS, ScriptRunnerEvent.SHUTDOWN, ], ) # We'll get two deltas: one for st.text(), and one for the # exception that gets thrown afterwards. elts = scriptrunner.elements() self.assertEqual(elts[0].WhichOneof("type"), "text") if show_error_details: self._assert_num_deltas(scriptrunner, 2) self.assertEqual(elts[1].WhichOneof("type"), "exception") else: self._assert_num_deltas(scriptrunner, 2) self.assertEqual(elts[1].WhichOneof("type"), "exception") exc_msg = elts[1].exception.message self.assertTrue(_GENERIC_UNCAUGHT_EXCEPTION_TEXT == exc_msg)
def test_rerun_coalesce_none_and_none(self): """Coalesce two null-WidgetStates rerun requests.""" reqs = ScriptRequests() # Request a rerun with null WidgetStates success = reqs.request_rerun(RerunData(widget_states=None)) self.assertTrue(success) self.assertEqual(ScriptRequestType.RERUN, reqs._state) # Request another reqs.request_rerun(RerunData(widget_states=None)) self.assertTrue(success) self.assertEqual(ScriptRequestType.RERUN, reqs._state) # The resulting RerunData should have null widget_states self.assertEqual(RerunData(widget_states=None), reqs._rerun_data)
def test_run_script(self, filename, text): """Tests that we can run a script to completion.""" scriptrunner = TestScriptRunner(filename) scriptrunner.request_rerun(RerunData()) scriptrunner.start() scriptrunner.join() self._assert_no_exceptions(scriptrunner) self._assert_events( scriptrunner, [ ScriptRunnerEvent.SCRIPT_STARTED, ScriptRunnerEvent.ENQUEUE_FORWARD_MSG, ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS, ScriptRunnerEvent.SHUTDOWN, ], ) self._assert_text_deltas(scriptrunner, [text]) # The following check is a requirement for the CodeHasher to # work correctly. The CodeHasher is scoped to # files contained in the directory of __main__.__file__, which we # assume is the main script directory. self.assertEqual( scriptrunner._session_data.main_script_path, sys.modules["__main__"].__file__, (" ScriptRunner should set the __main__.__file__" "attribute correctly"), )
def test_maybe_handle_execution_control_request(self): """maybe_handle_execution_control_request should no-op if called from another thread. """ runner = TestScriptRunner("not_a_script.py") runner._execing = True # Mock ScriptRequests.on_scriptrunner_yield(). It will return a fake # rerun request. requests_mock = MagicMock() requests_mock.on_scriptrunner_yield = MagicMock( return_value=ScriptRequest(ScriptRequestType.RERUN, RerunData())) runner._requests = requests_mock # If _is_in_script_thread is False, our request shouldn't get popped runner._is_in_script_thread = MagicMock(return_value=False) runner._maybe_handle_execution_control_request() requests_mock.on_scriptrunner_yield.assert_not_called() # If _is_in_script_thread is True, our rerun request should get # popped (and this will result in a RerunException being raised). runner._is_in_script_thread = MagicMock(return_value=True) with self.assertRaises(RerunException): runner._maybe_handle_execution_control_request() requests_mock.on_scriptrunner_yield.assert_called_once()
def test_dont_enqueue_with_pending_script_request(self): """No ForwardMsgs are enqueued when the ScriptRunner has a STOP or RERUN request. """ # Create a ScriptRunner and pretend that we've already started # executing. runner = TestScriptRunner("not_a_script.py") runner._is_in_script_thread = MagicMock(return_value=True) runner._execing = True runner._requests._state = ScriptRequestType.CONTINUE # Enqueue a ForwardMsg on the runner, and ensure it's delivered # to event listeners. (We're not stopped yet.) mock_msg = MagicMock() runner._enqueue_forward_msg(mock_msg) self._assert_forward_msgs(runner, [mock_msg]) runner.clear_forward_msgs() # Now, "stop" our ScriptRunner. Enqueuing should result in # a StopException being raised, and no message enqueued. runner._requests.request_stop() with self.assertRaises(StopException): runner._enqueue_forward_msg(MagicMock()) self._assert_forward_msgs(runner, []) # And finally, request a rerun. Enqueuing should result in # a RerunException being raised and no message enqueued. runner._requests = ScriptRequests() runner.request_rerun(RerunData()) with self.assertRaises(RerunException): runner._enqueue_forward_msg(MagicMock()) self._assert_forward_msgs(runner, [])
def test_rerun_while_stopped(self): """Requesting a rerun while STOPPED will return False.""" reqs = ScriptRequests() reqs.request_stop() success = reqs.request_rerun(RerunData()) self.assertFalse(success) self.assertEqual(ScriptRequestType.STOP, reqs._state)
def test_rerun_with_no_scriptrunner(self, mock_create_scriptrunner: MagicMock): """If we don't have a ScriptRunner, a rerun request will result in one being created.""" session = _create_test_session() session.request_rerun(None) mock_create_scriptrunner.assert_called_once_with(RerunData())
def test_rerun_with_stopped_scriptrunner(self, mock_create_scriptrunner: MagicMock): """If have a ScriptRunner but it's shutting down and cannot handle new rerun requests, we'll create a new ScriptRunner.""" session = _create_test_session() mock_stopped_scriptrunner = MagicMock(spec=ScriptRunner) mock_stopped_scriptrunner.request_rerun = MagicMock(return_value=False) session._scriptrunner = mock_stopped_scriptrunner session.request_rerun(None) # The stopped ScriptRunner will reject the request... mock_stopped_scriptrunner.request_rerun.assert_called_once_with(RerunData()) # So we'll create a new ScriptRunner. mock_create_scriptrunner.assert_called_once_with(RerunData())
def test_rerun_while_running(self): """Requesting a rerun while in CONTINUE state will always succeed.""" reqs = ScriptRequests() rerun_data = RerunData(query_string="test_query_string") success = reqs.request_rerun(rerun_data) self.assertTrue(success) self.assertEqual(ScriptRequestType.RERUN, reqs._state) self.assertEqual(rerun_data, reqs._rerun_data)
def test_widgets(self): """Tests that widget values behave as expected.""" scriptrunner = TestScriptRunner("widgets_script.py") scriptrunner.request_rerun(RerunData()) scriptrunner.start() # Default widget values require_widgets_deltas([scriptrunner]) self._assert_text_deltas( scriptrunner, ["False", "ahoy!", "0", "False", "loop_forever"]) # Update widgets states = WidgetStates() w1_id = scriptrunner.get_widget_id("checkbox", "checkbox") _create_widget(w1_id, states).bool_value = True w2_id = scriptrunner.get_widget_id("text_area", "text_area") _create_widget(w2_id, states).string_value = "matey!" w3_id = scriptrunner.get_widget_id("radio", "radio") _create_widget(w3_id, states).int_value = 2 w4_id = scriptrunner.get_widget_id("button", "button") _create_widget(w4_id, states).trigger_value = True # Explicitly clear deltas before re-running, to prevent a race # condition. (The ScriptRunner will clear the deltas when it # starts the re-run, but if that doesn't happen before # require_widgets_deltas() starts polling the ScriptRunner's deltas, # it will see stale deltas from the last run.) scriptrunner.clear_forward_msgs() scriptrunner.request_rerun(RerunData(widget_states=states)) require_widgets_deltas([scriptrunner]) self._assert_text_deltas( scriptrunner, ["True", "matey!", "2", "True", "loop_forever"]) # Rerun with previous values. Our button should be reset; # everything else should be the same. scriptrunner.clear_forward_msgs() scriptrunner.request_rerun(RerunData()) require_widgets_deltas([scriptrunner]) self._assert_text_deltas( scriptrunner, ["True", "matey!", "2", "False", "loop_forever"]) scriptrunner.request_stop() scriptrunner.join() self._assert_no_exceptions(scriptrunner)
def request_rerun(self, client_state: Optional[ClientState]) -> None: """Signal that we're interested in running the script. If the script is not already running, it will be started immediately. Otherwise, a rerun will be requested. Parameters ---------- client_state : streamlit.proto.ClientState_pb2.ClientState | None The ClientState protobuf to run the script with, or None to use previous client state. """ if self._state == AppSessionState.SHUTDOWN_REQUESTED: LOGGER.warning("Discarding rerun request after shutdown") return if client_state: rerun_data = RerunData(client_state.query_string, client_state.widget_states) else: rerun_data = RerunData() if self._scriptrunner is not None: if bool(config.get_option("runner.fastReruns")): # If fastReruns is enabled, we don't send rerun requests to our # existing ScriptRunner. Instead, we tell it to shut down. We'll # then spin up a new ScriptRunner, below, to handle the rerun # immediately. self._scriptrunner.request_stop() self._scriptrunner = None else: # fastReruns is not enabled. Send our ScriptRunner a rerun # request. If the request is accepted, we're done. success = self._scriptrunner.request_rerun(rerun_data) if success: return # If we are here, then either we have no ScriptRunner, or our # current ScriptRunner is shutting down and cannot handle a rerun # request - so we'll create and start a new ScriptRunner. self._create_scriptrunner(rerun_data)
def test_coalesce_rerun(self): """Tests that multiple pending rerun requests get coalesced.""" scriptrunner = TestScriptRunner("good_script.py") scriptrunner.request_rerun(RerunData()) scriptrunner.request_rerun(RerunData()) scriptrunner.request_rerun(RerunData()) scriptrunner.start() scriptrunner.join() self._assert_no_exceptions(scriptrunner) self._assert_events( scriptrunner, [ ScriptRunnerEvent.SCRIPT_STARTED, ScriptRunnerEvent.ENQUEUE_FORWARD_MSG, ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS, ScriptRunnerEvent.SHUTDOWN, ], ) self._assert_text_deltas(scriptrunner, [text_utf])
def test_calls_widget_callbacks(self, patched_call_callbacks): """Before a script is rerun, we call callbacks for any widgets whose value has changed. """ scriptrunner = TestScriptRunner("widgets_script.py") scriptrunner.request_rerun(RerunData()) scriptrunner.start() # Default widget values require_widgets_deltas([scriptrunner]) self._assert_text_deltas( scriptrunner, ["False", "ahoy!", "0", "False", "loop_forever"]) patched_call_callbacks.assert_not_called() # Update widgets states = WidgetStates() w1_id = scriptrunner.get_widget_id("checkbox", "checkbox") _create_widget(w1_id, states).bool_value = True w2_id = scriptrunner.get_widget_id("text_area", "text_area") _create_widget(w2_id, states).string_value = "matey!" w3_id = scriptrunner.get_widget_id("radio", "radio") _create_widget(w3_id, states).int_value = 2 w4_id = scriptrunner.get_widget_id("button", "button") _create_widget(w4_id, states).trigger_value = True # Explicitly clear deltas before re-running, to prevent a race # condition. (The ScriptRunner will clear the deltas when it # starts the re-run, but if that doesn't happen before # require_widgets_deltas() starts polling the ScriptRunner's deltas, # it will see stale deltas from the last run.) scriptrunner.clear_forward_msgs() scriptrunner.request_rerun(RerunData(widget_states=states)) require_widgets_deltas([scriptrunner]) patched_call_callbacks.assert_called_once() self._assert_text_deltas( scriptrunner, ["True", "matey!", "2", "True", "loop_forever"]) scriptrunner.request_stop() scriptrunner.join()
def test_rerun_coalesce_none_and_widgets(self): """Coalesce a null-WidgetStates rerun request with a non-null-WidgetStates request. """ reqs = ScriptRequests() # Request a rerun with null WidgetStates. success = reqs.request_rerun(RerunData(widget_states=None)) self.assertTrue(success) # Request a rerun with non-null WidgetStates. states = WidgetStates() _create_widget("trigger", states).trigger_value = True _create_widget("int", states).int_value = 123 success = reqs.request_rerun(RerunData(widget_states=states)) self.assertTrue(success) # The null WidgetStates request will be overwritten. result_states = reqs._rerun_data.widget_states self.assertEqual(True, _get_widget("trigger", result_states).trigger_value) self.assertEqual(123, _get_widget("int", result_states).int_value)
def test_stop_script(self): """Tests that we can stop a script while it's running.""" scriptrunner = TestScriptRunner("infinite_loop.py") scriptrunner.request_rerun(RerunData()) scriptrunner.start() time.sleep(0.1) scriptrunner.request_rerun(RerunData()) # This test will fail if the script runner does not execute the infinite # script's write call at least once during the final script run. # The script runs forever, and when we enqueue a rerun it forcibly # stops execution and runs some cleanup. If we do not wait for the # forced GC to finish, the script won't start running before we stop # the script runner, so the expected delta is never created. time.sleep(1) scriptrunner.request_stop() scriptrunner.join() self._assert_no_exceptions(scriptrunner) # We use _assert_control_events, and not _assert_events, # because the infinite loop will fire an indeterminate number of # ForwardMsg enqueue requests. Those ForwardMsgs will all be ultimately # coalesced down to a single message by the ForwardMsgQueue, which is # why the "_assert_text_deltas" call, below, just asserts the existence # of a single ForwardMsg. self._assert_control_events( scriptrunner, [ ScriptRunnerEvent.SCRIPT_STARTED, ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS, ScriptRunnerEvent.SCRIPT_STARTED, ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS, ScriptRunnerEvent.SHUTDOWN, ], ) self._assert_text_deltas(scriptrunner, ["loop_forever"])
def test_invalidating_cache(self): """Test that st.caches are cleared when a dependency changes.""" # Make sure there are no caches from other tests. caching._mem_caches.clear() # Run st_cache_script. runner = TestScriptRunner("st_cache_script.py") runner.request_rerun(RerunData()) runner.start() runner.join() # The script has 5 cached functions, each of which writes out # som text. self._assert_text_deltas( runner, [ "cached function called", "cached function called", "cached function called", "cached function called", "cached_depending_on_not_yet_defined called", ], ) # Run a slightly different script on a second runner. runner = TestScriptRunner("st_cache_script_changed.py") runner.request_rerun(RerunData()) runner.start() runner.join() # The cached functions should not have been called on this second run, # except for the one that has actually changed. self._assert_text_deltas( runner, [ "cached_depending_on_not_yet_defined called", ], )
def test_rerun_with_active_scriptrunner(self, mock_create_scriptrunner: MagicMock): """If we have an active ScriptRunner, it receives rerun requests.""" session = _create_test_session() mock_active_scriptrunner = MagicMock(spec=ScriptRunner) mock_active_scriptrunner.request_rerun = MagicMock(return_value=True) session._scriptrunner = mock_active_scriptrunner session.request_rerun(None) # The active ScriptRunner will accept the rerun request... mock_active_scriptrunner.request_rerun.assert_called_once_with(RerunData()) # So _create_scriptrunner should not be called. mock_create_scriptrunner.assert_not_called()
def test_remove_nonexistent_elements(self): """Tests that nonexistent elements are removed from widget cache after script run.""" widget_id = "nonexistent_widget_id" # Run script, sending in a WidgetStates containing our fake widget ID. scriptrunner = TestScriptRunner("good_script.py") states = WidgetStates() _create_widget(widget_id, states).string_value = "streamlit" scriptrunner.request_rerun(RerunData(widget_states=states)) scriptrunner.start() # At this point, scriptrunner should have finished running, detected # that our widget_id wasn't in the list of widgets found this run, and # culled it. Ensure widget cache no longer holds our widget ID. self.assertIsNone(scriptrunner._session_state.get(widget_id, None))
def test_compile_error(self): """Tests that we get an exception event when a script can't compile.""" scriptrunner = TestScriptRunner("compile_error.py.txt") scriptrunner.request_rerun(RerunData()) scriptrunner.start() scriptrunner.join() self._assert_no_exceptions(scriptrunner) self._assert_events( scriptrunner, [ ScriptRunnerEvent.SCRIPT_STARTED, ScriptRunnerEvent.SCRIPT_STOPPED_WITH_COMPILE_ERROR, ScriptRunnerEvent.SHUTDOWN, ], ) self._assert_text_deltas(scriptrunner, [])
def test_missing_script(self): """Tests that we get an exception event when a script doesn't exist.""" scriptrunner = TestScriptRunner("i_do_not_exist.py") scriptrunner.request_rerun(RerunData()) scriptrunner.start() scriptrunner.join() self._assert_no_exceptions(scriptrunner) self._assert_events( scriptrunner, [ ScriptRunnerEvent.SCRIPT_STARTED, ScriptRunnerEvent.SCRIPT_STOPPED_WITH_COMPILE_ERROR, ScriptRunnerEvent.SHUTDOWN, ], ) self._assert_text_deltas(scriptrunner, [])
def test_query_string_saved(self): scriptrunner = TestScriptRunner("good_script.py") scriptrunner.request_rerun(RerunData(query_string="foo=bar")) scriptrunner.start() scriptrunner.join() self._assert_no_exceptions(scriptrunner) self._assert_events( scriptrunner, [ ScriptRunnerEvent.SCRIPT_STARTED, ScriptRunnerEvent.ENQUEUE_FORWARD_MSG, ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS, ScriptRunnerEvent.SHUTDOWN, ], ) shutdown_data = scriptrunner.event_data[-1] self.assertEqual(shutdown_data["client_state"].query_string, "foo=bar")