예제 #1
0
    def test_handle_uncaught_app_exception_with_rich(self):
        """Test if the exception is logged with rich enabled and disabled."""
        exc = Exception("boom!")
        with testutil.patch_config_options({"logger.enableRich": True}):
            with io.StringIO() as buf:
                # Capture stdout logs (rich logs to stdout)
                with contextlib.redirect_stdout(buf):
                    handle_uncaught_app_exception(exc)
                # Capture the stdout output
                captured_output = buf.getvalue()

                assert "Exception:" in captured_output
                assert "boom!" in captured_output
                # Uncaught app exception is only used by the non-rich exception logging
                assert "Uncaught app exception" not in captured_output
        with testutil.patch_config_options({"logger.enableRich": False}):
            with io.StringIO() as buf:
                # Capture stdout logs
                with contextlib.redirect_stdout(buf):
                    handle_uncaught_app_exception(exc)
                # Capture the stdout output
                captured_output = buf.getvalue()

                # With rich deactivated, the exception is not logged to stdout
                assert "Exception:" not in captured_output
                assert "boom!" not in captured_output
예제 #2
0
    def test_runtime_error(self, show_tracebacks: bool):
        """Tests that we correctly handle scripts with runtime errors."""
        with testutil.patch_config_options(
            {"client.showTracebacks": show_tracebacks}):
            scriptrunner = TestScriptRunner("runtime_error.py")
            scriptrunner.enqueue_rerun()
            scriptrunner.start()
            scriptrunner.join()

            self._assert_no_exceptions(scriptrunner)
            self._assert_events(
                scriptrunner,
                [
                    ScriptRunnerEvent.SCRIPT_STARTED,
                    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._assert_num_deltas(scriptrunner, 2)
            self.assertEqual(elts[0].WhichOneof("type"), "text")

            if show_tracebacks:
                self.assertEqual(elts[1].WhichOneof("type"), "exception")
            else:
                self.assertEqual(elts[1].WhichOneof("type"), "alert")
                self.assertEqual(elts[1].alert.format, Alert.ERROR)
                self.assertEqual(elts[1].alert.body,
                                 _GENERIC_UNCAUGHT_EXCEPTION_TEXT)
예제 #3
0
    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)
예제 #4
0
    def test_watch_file(self, mock_event_watcher, mock_polling_watcher):
        """Test all possible outcomes of both `get_file_watcher_class` and
        `watch_file`, based on config.fileWatcherType and whether
        `watchdog_available` is true.
        """
        subtest_params = [
            (None, False, None),
            (None, True, None),
            ("poll", False, mock_polling_watcher),
            ("poll", True, mock_polling_watcher),
            ("watchdog", False, None),
            ("watchdog", True, mock_event_watcher),
            ("auto", False, mock_polling_watcher),
            ("auto", True, mock_event_watcher),
        ]
        for watcher_config, watchdog_available, file_watcher_class in subtest_params:
            test_name = f"config.fileWatcherType={watcher_config}, watcher_available={watchdog_available}"
            with self.subTest(test_name):
                with patch_config_options(
                    {"server.fileWatcherType": watcher_config}), patch(
                        "streamlit.watcher.file_watcher.watchdog_available",
                        watchdog_available,
                    ):
                    # Test get_file_watcher_class() result
                    self.assertEqual(file_watcher_class,
                                     get_file_watcher_class())

                    # Test watch_file(). If file_watcher_class is None,
                    # nothing should happen. Otherwise, file_watcher_class
                    # should be called with the watch_file params.
                    on_file_changed = Mock()
                    watch_file("some/file/path", on_file_changed)
                    if file_watcher_class is not None:
                        file_watcher_class.assert_called_with(
                            "some/file/path", on_file_changed)
예제 #5
0
    def test_uncaught_exception_no_details(self, mock_st_error,
                                           mock_st_exception):
        """If client.showErrorDetails is false, uncaught app errors are logged,
        and a generic error message is printed to the frontend."""
        with testutil.patch_config_options({"client.showErrorDetails": False}):
            exc = RuntimeError("boom!")
            handle_uncaught_app_exception(exc)

            mock_st_error.assert_not_called()
            mock_st_exception.assert_called_once()
예제 #6
0
    def test_uncaught_exception_show_details(self, mock_st_error,
                                             mock_st_exception):
        """If client.showErrorDetails is true, uncaught app errors print
        to the frontend."""
        with testutil.patch_config_options({"client.showErrorDetails": True}):
            exc = RuntimeError("boom!")
            handle_uncaught_app_exception(exc)

            mock_st_error.assert_not_called()
            mock_st_exception.assert_called_once_with(exc)
예제 #7
0
    def test_uncaught_exception_no_tracebacks(self, mock_st_error,
                                              mock_st_exception):
        """If client.showTracebacks is false, uncaught app errors are logged,
        and a generic error message is printed to the frontend."""
        with testutil.patch_config_options({"client.showTracebacks": False}):
            exc = RuntimeError("boom!")
            handle_uncaught_app_exception(exc)

            mock_st_exception.assert_not_called()
            mock_st_error.assert_called_once_with(
                _GENERIC_UNCAUGHT_EXCEPTION_TEXT)
예제 #8
0
    def test_mutation_warning_text(self, show_tracebacks: bool):
        with testutil.patch_config_options(
            {"client.showTracebacks": show_tracebacks}):

            @st.cache
            def mutation_warning_func():
                return []

            a = mutation_warning_func()
            a.append("mutated!")
            mutation_warning_func()

            if show_tracebacks:
                el = self.get_delta_from_queue(-1).new_element
                self.assertEqual(el.exception.type,
                                 "CachedObjectMutationWarning")

                self.assertEqual(
                    normalize_md(el.exception.message),
                    normalize_md("""
Return value of `mutation_warning_func()` was mutated between runs.

By default, Streamlit\'s cache should be treated as immutable, or it may behave
in unexpected ways. You received this warning because Streamlit detected that
an object returned by `mutation_warning_func()` was mutated outside of
`mutation_warning_func()`.

How to fix this:
* If you did not mean to mutate that return value:
  - If possible, inspect your code to find and remove that mutation.
  - Otherwise, you could also clone the returned value so you can freely
    mutate it.
* If you actually meant to mutate the return value and know the consequences of
doing so, annotate the function with `@st.cache(allow_output_mutation=True)`.

For more information and detailed solutions check out [our
documentation.](https://docs.streamlit.io/en/latest/caching.html)
                    """),
                )
                self.assertNotEqual(len(el.exception.stack_trace), 0)
                self.assertEqual(el.exception.message_is_markdown, True)
                self.assertEqual(el.exception.is_warning, True)
            else:
                el = self.get_delta_from_queue(-1).new_element
                self.assertEqual(el.WhichOneof("type"), "alert")
                self.assertEqual(el.alert.format, Alert.ERROR)
                self.assertEqual(el.alert.body,
                                 _GENERIC_UNCAUGHT_EXCEPTION_TEXT)
예제 #9
0
    def test_st_exception(self, show_tracebacks: bool):
        """Test st.exception."""
        # client.showTracebacks has no effect on code that calls
        # st.exception directly. This test should have the same result
        # regardless fo the config option.
        with testutil.patch_config_options(
            {"client.showTracebacks": show_tracebacks}):
            e = RuntimeError("Test Exception")
            st.exception(e)

            el = self.get_delta_from_queue().new_element
            self.assertEqual(el.exception.type, "RuntimeError")
            self.assertEqual(el.exception.message, "Test Exception")
            # We will test stack_trace when testing
            # streamlit.elements.exception_element
            self.assertEqual(el.exception.stack_trace, [])
예제 #10
0
    def test_watch_file(self, mock_event_watcher, mock_polling_watcher):
        """Test all possible outcomes of both `get_default_path_watcher_class` and
        `watch_file`, based on config.fileWatcherType and whether
        `watchdog_available` is true.
        """
        subtest_params = [
            (None, False, NoOpPathWatcher),
            (None, True, NoOpPathWatcher),
            ("poll", False, mock_polling_watcher),
            ("poll", True, mock_polling_watcher),
            ("watchdog", False, NoOpPathWatcher),
            ("watchdog", True, mock_event_watcher),
            ("auto", False, mock_polling_watcher),
            ("auto", True, mock_event_watcher),
        ]
        for watcher_config, watchdog_available, path_watcher_class in subtest_params:
            test_name = f"config.fileWatcherType={watcher_config}, watcher_available={watchdog_available}"
            with self.subTest(test_name):
                with patch_config_options(
                    {"server.fileWatcherType": watcher_config}), patch(
                        "streamlit.watcher.path_watcher.watchdog_available",
                        watchdog_available,
                    ):
                    # Test get_default_path_watcher_class() result
                    self.assertEqual(path_watcher_class,
                                     get_default_path_watcher_class())

                    # Test watch_file(). If path_watcher_class is
                    # NoOpPathWatcher, nothing should happen. Otherwise,
                    # path_watcher_class should be called with the watch_file
                    # params.
                    on_file_changed = Mock()
                    watching_file = watch_file("some/file/path",
                                               on_file_changed)
                    if path_watcher_class is not NoOpPathWatcher:
                        path_watcher_class.assert_called_with(
                            "some/file/path",
                            on_file_changed,
                            glob_pattern=None,
                            allow_nonexistent=False,
                        )
                        self.assertTrue(watching_file)
                    else:
                        self.assertFalse(watching_file)
예제 #11
0
    def test_enqueue(self, _, install_tracer):
        """Make sure we try to handle execution control requests whenever
        our _enqueue function is called, unless "runner.installTracer" is set.
        """
        with testutil.patch_config_options(
            {"runner.installTracer": install_tracer}):
            # Create a TestScriptRunner. We won't actually be starting its
            # script thread - instead, we'll manually call _enqueue on it, and
            # pretend we're in the script thread.
            runner = TestScriptRunner("not_a_script.py")
            runner._is_in_script_thread = MagicMock(return_value=True)

            maybe_handle_execution_control_request_mock = MagicMock()
            runner._maybe_handle_execution_control_request = (
                maybe_handle_execution_control_request_mock)

            enqueue_forward_msg_mock = MagicMock()
            runner._enqueue_forward_msg = enqueue_forward_msg_mock

            # Enqueue a message on the runner
            mock_msg = MagicMock()
            runner._enqueue(mock_msg)

            # Ensure the message was "bubbled up" to the enqueue callback.
            enqueue_forward_msg_mock.assert_called_once_with(mock_msg)

            # If "install_tracer" is true, maybe_handle_execution_control_request
            # should not be called by the enqueue function. (In reality, it will
            # still be called once in the tracing callback But in this test
            # we're not actually installing a tracer - the script is not being
            # run.) If "install_tracer" is false, the function should be called
            # once.
            expected_call_count = 0 if install_tracer else 1
            self.assertEqual(
                expected_call_count,
                maybe_handle_execution_control_request_mock.call_count,
            )
예제 #12
0
    def test_yield_on_enqueue(self, _, install_tracer: bool):
        """Make sure we try to handle execution control requests whenever
        our _enqueue_forward_msg function is called, unless "runner.installTracer" is set.
        """
        with testutil.patch_config_options(
            {"runner.installTracer": install_tracer}):
            # Create a TestScriptRunner. We won't actually be starting its
            # script thread - instead, we'll manually call _enqueue_forward_msg on it, and
            # pretend we're in the script thread.
            runner = TestScriptRunner("not_a_script.py")
            runner._is_in_script_thread = MagicMock(return_value=True)

            # Mock the call to _maybe_handle_execution_control_request.
            # This is what we're testing gets called or not.
            maybe_handle_execution_control_request_mock = MagicMock()
            runner._maybe_handle_execution_control_request = (
                maybe_handle_execution_control_request_mock)

            # Enqueue a ForwardMsg on the runner
            mock_msg = MagicMock()
            runner._enqueue_forward_msg(mock_msg)

            # Ensure the ForwardMsg was delivered to event listeners.
            self._assert_forward_msgs(runner, [mock_msg])

            # If "install_tracer" is true, maybe_handle_execution_control_request
            # should not be called by the enqueue function. (In reality, it will
            # still be called once in the tracing callback But in this test
            # we're not actually installing a tracer - the script is not being
            # run.) If "install_tracer" is false, the function should be called
            # once.
            expected_call_count = 0 if install_tracer else 1
            self.assertEqual(
                expected_call_count,
                maybe_handle_execution_control_request_mock.call_count,
            )