import traceback import pytest from crawlee.statistics._error_tracker import ErrorTracker @pytest.mark.parametrize( ('error_tracker', 'expected_unique_errors'), [ (ErrorTracker(), 5), (ErrorTracker(show_file_and_line_number=False), 4), (ErrorTracker(show_error_name=False), 4), (ErrorTracker(show_error_message=False), 3), (ErrorTracker(show_error_name=False, show_file_and_line_number=False), 3), (ErrorTracker(show_file_and_line_number=False, show_error_message=False), 2), (ErrorTracker(show_error_name=False, show_file_and_line_number=False, show_error_message=False), 1), ], ) async def test_error_tracker_counts(error_tracker: ErrorTracker, expected_unique_errors: int) -> None: """Use different settings of `error_tracker` and test unique errors count.""" for error in [ Exception('Some value error abc'), ValueError('Some value error abc'), # Different type, different error ValueError('Some value error cde'), # Same type and similar message to previous, considered the same. ValueError( 'Another value error efg' ), # Same type, but too different message to previous, considered different. ValueError(), # Same type but don't have message, considered different. ]: try: raise error # Errors raised on same line except Exception as e: # noqa:PERF203 await error_tracker.add(e) try: raise ValueError('Some value error abc') # Same as one previous error, but different line. except Exception as e: await error_tracker.add(e) assert error_tracker.total == 6 assert error_tracker.unique_error_count == expected_unique_errors @pytest.mark.parametrize( ('message_1', 'message_2', 'expected_generic_message'), [ ('Some error number 123', 'Some error number 456', 'Some error number ***'), ('Some error number 123 456', 'Some error number 123 456 789', 'Some error number 123 456 ***'), ('Some error number 0 0 0', 'Some error number 1 0 1', 'Some error number *** 0 ***'), ], ) async def test_error_tracker_similar_messages_full_stack( message_1: str, message_2: str, expected_generic_message: str ) -> None: """Test that similar messages collapse into same group with generic name that contains wildcard symbols.""" error_tracker = ErrorTracker() for error in [ KeyError(message_1), KeyError(message_1), KeyError(message_1), ValueError(message_1), ValueError(message_2), RuntimeError(message_2), ]: try: raise error # Errors raised on the same line except Exception as e: # noqa:PERF203 await error_tracker.add(e) line = traceback.extract_tb(e.__traceback__)[0].lineno file_name = __file__.split('/')[-1] errors = error_tracker.get_most_common_errors() assert errors[0][0] == f'{file_name}:{line}:KeyError:{message_1}' assert errors[0][1] == 3 assert errors[1][0] == f'{file_name}:{line}:ValueError:{expected_generic_message}' assert errors[1][1] == 2 assert errors[2][0] == f'{file_name}:{line}:RuntimeError:{message_2}' assert errors[2][1] == 1 @pytest.mark.parametrize( ('show_full_message', 'expected_message'), [ (True, 'Error line 1\n Error line 2'), (False, 'Error line 1'), ], ) async def test_show_full_message(*, show_full_message: bool, expected_message: str) -> None: """Test error message settings with both options of `show_full_message`.""" error_tracker = ErrorTracker( show_error_name=False, show_file_and_line_number=False, show_full_message=show_full_message ) try: raise RuntimeError('Error line 1\n Error line 2') # Errors raised on the same line except Exception as e: await error_tracker.add(e) assert error_tracker.get_most_common_errors()[0][0] == expected_message async def test_error_tracker_with_errors_chain() -> None: """Test error tracker with errors chain.""" error_tracker = ErrorTracker(show_error_name=False, show_file_and_line_number=False, show_full_message=True) try: raise ZeroDivisionError('Zero division error') # Errors raised on the same line except Exception as e: try: raise ValueError from e except Exception as e: await error_tracker.add(e) assert error_tracker.get_most_common_errors()[0][0] == 'Zero division error'