Пример #1
0
def test_init(mocked_init: Mock, sentry_reporter: SentryReporter):
    # test that `init` method set all necessary variables and calls `sentry_sdk.init()`
    sentry_reporter.init(sentry_url='url', release_version='release', scrubber=SentryScrubber(),
                         strategy=SentryStrategy.SEND_SUPPRESSED)
    assert sentry_reporter.scrubber
    assert sentry_reporter.global_strategy == SentryStrategy.SEND_SUPPRESSED
    mocked_init.assert_called_once()
Пример #2
0
def test_before_send_scrubber_exists(sentry_reporter: SentryReporter):
    # test that in case of a set scrubber, it will be called for scrubbing an event
    event = {'some': 'event'}

    sentry_reporter.global_strategy = SentryStrategy.SEND_ALLOWED
    sentry_reporter.scrubber = Mock()
    assert sentry_reporter._before_send(event, None)
    sentry_reporter.scrubber.scrub_event.assert_called_with(event)
Пример #3
0
def test_get_confirmation(sentry_reporter: SentryReporter):
    # test that `get_confirmation` calls `QApplication` and `QMessageBox` from `PyQt5.QtWidgets`
    mocked_QApplication = Mock()
    mocked_QMessageBox = MagicMock()

    with patch_import('PyQt5.QtWidgets', strict=True, QApplication=mocked_QApplication, QMessageBox=mocked_QMessageBox):
        sentry_reporter.get_confirmation(Exception('test'))
        mocked_QApplication.assert_called()
        mocked_QMessageBox.assert_called()
Пример #4
0
def run_tribler_core_session(api_port,
                             api_key,
                             state_dir,
                             gui_test_mode=False):
    """
    This method will start a new Tribler session.
    Note that there is no direct communication between the GUI process and the core: all communication is performed
    through the HTTP API.
    """
    logger.info(f'Start tribler core. API port: "{api_port}". '
                f'API key: "{api_key}". State dir: "{state_dir}". '
                f'Core test mode: "{gui_test_mode}"')

    config = TriblerConfig.load(file=state_dir / CONFIG_FILE_NAME,
                                state_dir=state_dir,
                                reset_config_on_error=True)
    config.gui_test_mode = gui_test_mode

    if SentryReporter.is_in_test_mode():
        default_core_exception_handler.sentry_reporter.global_strategy = SentryStrategy.SEND_ALLOWED

    config.api.http_port = int(api_port)
    # If the API key is set to an empty string, it will remain disabled
    if config.api.key not in ('', api_key):
        config.api.key = api_key
        config.write(
        )  # Immediately write the API key so other applications can use it
    config.api.http_enabled = True

    priority_order = config.resource_monitor.cpu_priority
    set_process_priority(pid=os.getpid(), priority_order=priority_order)

    # Enable tracer if --trace-debug or --trace-exceptions flag is present in sys.argv
    log_dir = config.general.get_path_as_absolute('log_dir', config.state_dir)
    trace_logger = check_and_enable_code_tracing('core', log_dir)

    logging.getLogger('asyncio').setLevel(logging.WARNING)

    if sys.platform.startswith('win'):
        # TODO for the moment being, we use the SelectorEventLoop on Windows, since with the ProactorEventLoop, ipv8
        # peer discovery becomes unstable. Also see issue #5485.
        asyncio.set_event_loop(asyncio.SelectorEventLoop())

    loop = asyncio.get_event_loop()
    exception_handler = default_core_exception_handler
    loop.set_exception_handler(exception_handler.unhandled_error_observer)

    try:
        loop.run_until_complete(
            core_session(config, components=list(components_gen(config))))
    finally:
        if trace_logger:
            trace_logger.close()

        # Flush the logs to the file before exiting
        for handler in logging.getLogger().handlers:
            handler.flush()
Пример #5
0
def test_event_from_exception(mocked_capture_exception: Mock, sentry_reporter: SentryReporter):
    # test that `event_from_exception` returns '{}' in case of an empty exception
    assert sentry_reporter.event_from_exception(None) == {}

    # test that `event_from_exception` calls `capture_exception` from `sentry_sdk`
    exception = Exception('test')
    sentry_reporter.thread_strategy = Mock()

    def capture_exception(_):
        # this behaviour normally is way more complicated, but at the end, `capture_exception` should transform
        # the exception to a sentry event and this event should be stored in `sentry_reporter.last_event`
        sentry_reporter.last_event = {'sentry': 'event'}

    mocked_capture_exception.side_effect = capture_exception

    sentry_reporter.event_from_exception(exception)

    mocked_capture_exception.assert_called_with(exception)
    sentry_reporter.thread_strategy.set.assert_any_call(SentryStrategy.SEND_SUPPRESSED)
    assert sentry_reporter.last_event == {'sentry': 'event'}
Пример #6
0
def test_feedback_dialog(window):
    def screenshot_dialog():
        screenshot(dialog, name="feedback_dialog")
        dialog.close()

    reported_error = ReportedError('type', 'text', {})
    sentry_reporter = SentryReporter()
    dialog = FeedbackDialog(window, sentry_reporter, reported_error, "1.2.3",
                            23)
    dialog.closeEvent = lambda _: None  # Otherwise, the application will stop
    QTimer.singleShot(1000, screenshot_dialog)
    dialog.exec_()
Пример #7
0
def test_feedback_dialog_report_sent(window):
    def screenshot_dialog():
        screenshot(dialog, name="feedback_dialog")
        dialog.close()

    def on_report_sent():
        on_report_sent.did_send_report = True

    on_report_sent.did_send_report = False
    reported_error = ReportedError(
        '', 'Tribler GUI Test to test sending crash report works', {})
    sentry_reporter = SentryReporter()
    dialog = FeedbackDialog(window, sentry_reporter, reported_error, "1.2.3",
                            23)
    dialog.closeEvent = lambda _: None  # Otherwise, the application will stop
    dialog.on_report_sent = on_report_sent
    QTest.mouseClick(dialog.send_report_button, Qt.LeftButton)
    QTimer.singleShot(1000, screenshot_dialog)
    dialog.exec_()
    assert on_report_sent.did_send_report
Пример #8
0
def test_is_not_in_test_mode():
    assert SentryReporter.get_test_sentry_url() is None
    assert not SentryReporter.is_in_test_mode()
Пример #9
0
    def __init__(  # pylint: disable=too-many-arguments, too-many-locals
        self,
        parent,
        sentry_reporter: SentryReporter,
        reported_error: ReportedError,
        tribler_version,
        start_time,
        stop_application_on_close=True,
        additional_tags=None,
        retrieve_error_message_from_stacktrace=False,
    ):
        QDialog.__init__(self, parent)

        uic.loadUi(get_ui_file_path('feedback_dialog.ui'), self)

        self.setWindowTitle(tr("Unexpected error"))
        self.selected_item_index = 0
        self.tribler_version = tribler_version
        self.reported_error = reported_error
        self.scrubber = SentryScrubber()
        self.sentry_reporter = sentry_reporter
        self.stop_application_on_close = stop_application_on_close
        self.additional_tags = additional_tags
        self.retrieve_error_message_from_stacktrace = retrieve_error_message_from_stacktrace

        # Qt 5.2 does not have the setPlaceholderText property
        if hasattr(self.comments_text_edit, "setPlaceholderText"):
            placeholder = tr(
                "What were you doing before this crash happened? "
                "This information will help Tribler developers to figure out and fix the issue quickly."
            )
            self.comments_text_edit.setPlaceholderText(placeholder)

        def add_item_to_info_widget(key, value):
            item = QTreeWidgetItem(self.env_variables_list)
            item.setText(0, key)
            scrubbed_value = self.scrubber.scrub_text(value)
            item.setText(1, scrubbed_value)

        text_for_viewing = '\n'.join((
            reported_error.text,
            LONG_TEXT_DELIMITER,
            reported_error.long_text,
            CONTEXT_DELIMITER,
            reported_error.context,
        ))
        stacktrace = self.scrubber.scrub_text(text_for_viewing.rstrip())
        self.error_text_edit.setPlainText(stacktrace)
        connect(self.cancel_button.clicked, self.on_cancel_clicked)
        connect(self.send_report_button.clicked, self.on_send_clicked)

        # Add machine information to the tree widget
        add_item_to_info_widget('os.getcwd', f'{os.getcwd()}')
        add_item_to_info_widget('sys.executable', f'{sys.executable}')

        add_item_to_info_widget('os', os.name)
        add_item_to_info_widget('platform', sys.platform)
        add_item_to_info_widget('platform.details', platform.platform())
        add_item_to_info_widget('platform.machine', platform.machine())
        add_item_to_info_widget('python.version', sys.version)
        add_item_to_info_widget('indebug', str(__debug__))
        add_item_to_info_widget('tribler_uptime',
                                f"{time.time() - start_time}")

        for argv in sys.argv:
            add_item_to_info_widget('sys.argv', f'{argv}')

        for path in sys.path:
            add_item_to_info_widget('sys.path', f'{path}')

        for key in os.environ.keys():
            add_item_to_info_widget('os.environ', f'{key}: {os.environ[key]}')

        # Add recent requests to feedback dialog
        request_ind = 1
        for request, status_code in sorted(tribler_performed_requests,
                                           key=lambda rq: rq[0].time)[-30:]:
            add_item_to_info_widget(
                'request_%d' % request_ind,
                '%s %s %s (time: %s, code: %s)' %
                (request.url, request.method, request.raw_data, request.time,
                 status_code),
            )
            request_ind += 1

        # Add recent events to feedback dialog
        events_ind = 1
        for event, event_time in received_events[:30][::-1]:
            add_item_to_info_widget(
                'event_%d' % events_ind,
                f'{json.dumps(event)} (time: {event_time})')
            events_ind += 1

        # Users can remove specific lines in the report
        connect(self.env_variables_list.customContextMenuRequested,
                self.on_right_click_item)

        self.send_automatically = SentryReporter.is_in_test_mode()
        if self.send_automatically:
            self.stop_application_on_close = True
            self.on_send_clicked(True)
Пример #10
0
"""
This package contains the code for the GUI, written in pyQt.
"""
from tribler_core.sentry_reporter.sentry_reporter import SentryReporter

gui_sentry_reporter = SentryReporter()
Пример #11
0
def test_is_in_test_mode():
    assert SentryReporter.get_test_sentry_url() == 'url'
    assert SentryReporter.is_in_test_mode()
Пример #12
0
def test_capture_exception(mocked_capture_exception: Mock, sentry_reporter: SentryReporter):
    # test that `capture_exception` passes an exception to `sentry_sdk`
    exception = Exception('test')
    sentry_reporter.capture_exception(exception)
    mocked_capture_exception.assert_called_with(exception)
Пример #13
0
def test_get_confirmation_no_qt(sentry_reporter: SentryReporter):
    assert not sentry_reporter.get_confirmation(Exception('test'))
Пример #14
0
def test_before_send_allowed_with_confiration(sentry_reporter: SentryReporter):
    # test that in case of strategy==SentryStrategy.SEND_ALLOWED_WITH_CONFIRMATION, the event will be
    # sent after the positive confirmation
    sentry_reporter.global_strategy = SentryStrategy.SEND_ALLOWED_WITH_CONFIRMATION
    assert sentry_reporter._before_send({'some': 'event'}, None)
Пример #15
0
def test_add_breadcrumb(mocked_add_breadcrumb: Mock, sentry_reporter: SentryReporter):
    # test that `add_breadcrumb` passes all necessary arguments to `sentry_sdk`
    assert sentry_reporter.add_breadcrumb('message', 'category', 'level', named_arg='some')
    mocked_add_breadcrumb.assert_called_with({'message': 'message', 'category': 'category', 'level': 'level'},
                                             named_arg='some')
Пример #16
0
def test_ignore_logger(mocked_ignore_logger: Mock, sentry_reporter: SentryReporter):
    # test that `ignore_logger` calls `ignore_logger` from sentry_sdk
    sentry_reporter.ignore_logger('logger name')
    mocked_ignore_logger.assert_called_with('logger name')
Пример #17
0
def sentry_reporter():
    return SentryReporter()
Пример #18
0
def test_before_send_scrubber_doesnt_exists(sentry_reporter: SentryReporter):
    # test that in case of a missed scrubber, it will not be called
    sentry_reporter.scrubber = None
    sentry_reporter.global_strategy = SentryStrategy.SEND_ALLOWED
    assert sentry_reporter._before_send({'some': 'event'}, None)
Пример #19
0
def test_before_send_ignored_exceptions(sentry_reporter: SentryReporter):
    # test that in case of an ignored exception, `_before_send` will return None
    assert not sentry_reporter._before_send({'some': 'event'}, {'exc_info': [KeyboardInterrupt]})
Пример #20
0
def test_before_send_allowed(sentry_reporter: SentryReporter):
    # test that in case of strategy==SentryStrategy.SEND_ALLOWED, the event will be
    # sent without a confirmation
    sentry_reporter.global_strategy = SentryStrategy.SEND_ALLOWED
    assert sentry_reporter._before_send({'some': 'event'}, None)
Пример #21
0
class CoreExceptionHandler:
    """
    This class handles Python errors arising in the Core by catching them, adding necessary context,
    and sending them to the GUI through the events endpoint. It must be connected to the Asyncio loop.
    """
    def __init__(self):
        self.logger = logging.getLogger("CoreExceptionHandler")
        self.report_callback: Optional[Callable[[ReportedError], None]] = None
        self.unreported_error: Optional[ReportedError] = None
        self.sentry_reporter = SentryReporter()

    @staticmethod
    def _get_long_text_from(exception: Exception):
        with StringIO() as buffer:
            print_exception(type(exception),
                            exception,
                            exception.__traceback__,
                            file=buffer)
            return buffer.getvalue()

    @staticmethod
    def _is_ignored(exception: Exception):
        exception_class = exception.__class__
        error_number = exception.errno if hasattr(exception, 'errno') else None

        if (exception_class, error_number) in IGNORED_ERRORS_BY_CODE:
            return True

        if exception_class not in IGNORED_ERRORS_BY_REGEX:
            return False

        pattern = IGNORED_ERRORS_BY_REGEX[exception_class]
        return re.search(pattern, str(exception)) is not None

    def _create_exception_from(self, message: str):
        text = f'Received error without exception: {message}'
        self.logger.warning(text)
        return Exception(text)

    def unhandled_error_observer(self, _, context):
        """
        This method is called when an unhandled error in Tribler is observed.
        It broadcasts the tribler_exception event.
        """
        try:
            self.sentry_reporter.ignore_logger(self.logger.name)

            context = context.copy()
            should_stop = context.pop('should_stop', True)
            message = context.pop('message', 'no message')
            exception = context.pop(
                'exception', None) or self._create_exception_from(message)
            # Exception
            text = str(exception)
            if isinstance(exception, ComponentStartupException):
                should_stop = exception.component.tribler_should_stop_on_component_error
                exception = exception.__cause__

            if self._is_ignored(exception):
                self.logger.warning(exception)
                return

            long_text = self._get_long_text_from(exception)
            self.logger.error(
                f"Unhandled exception occurred! {exception}\n{long_text}")

            reported_error = ReportedError(
                type=exception.__class__.__name__,
                text=text,
                long_text=long_text,
                context=str(context),
                event=self.sentry_reporter.event_from_exception(exception)
                or {},
                should_stop=should_stop)
            if self.report_callback:
                self.report_callback(reported_error)  # pylint: disable=not-callable
            else:
                if not self.unreported_error:
                    # We only remember the first unreported error,
                    # as that was probably the root cause for # the crash
                    self.unreported_error = reported_error

        except Exception as ex:
            self.sentry_reporter.capture_exception(ex)
            raise ex
Пример #22
0
def test_before_send_no_event(sentry_reporter: SentryReporter):
    # test that in case of a None event, `_before_send` will never fail
    assert not sentry_reporter._before_send(None, None)
Пример #23
0
 def __init__(self):
     self.logger = logging.getLogger("CoreExceptionHandler")
     self.report_callback: Optional[Callable[[ReportedError], None]] = None
     self.unreported_error: Optional[ReportedError] = None
     self.sentry_reporter = SentryReporter()
Пример #24
0
def test_before_send_suppressed(sentry_reporter: SentryReporter):
    # test that in case of strategy==SentryStrategy.SEND_SUPPRESSED, the event will be stored in `self.last_event`
    sentry_reporter.global_strategy = SentryStrategy.SEND_SUPPRESSED
    assert not sentry_reporter._before_send({'some': 'event'}, None)
    assert sentry_reporter.last_event == {'some': 'event'}