diff --git a/podaac/merger/harmony/service.py b/podaac/merger/harmony/service.py index dfdec6dc..a38deae0 100644 --- a/podaac/merger/harmony/service.py +++ b/podaac/merger/harmony/service.py @@ -7,6 +7,7 @@ from urllib.parse import urlsplit from uuid import uuid4 import traceback +import sys from harmony_service_lib.adapter import BaseHarmonyAdapter from harmony_service_lib.util import bbox_to_geometry, stage @@ -23,9 +24,16 @@ class ConciseException(HarmonyException): - """Base class for exceptions in the Harmony GDAL Adapter.""" - + """Concise Exception class for custom error messages to see in harmony api calls.""" def __init__(self, original_exception): + # Ensure we can extract traceback information + if original_exception.__traceback__ is None: + # Capture the current traceback if not already present + try: + raise original_exception + except type(original_exception): + original_exception.__traceback__ = sys.exc_info()[2] + # Extract the last traceback entry (most recent call) for the error location tb = traceback.extract_tb(original_exception.__traceback__)[-1] @@ -39,7 +47,11 @@ def __init__(self, original_exception): readable_message = (f"Error in file '{filename}', line {lineno}, in function '{funcname}': " f"{error_msg}") - super().__init__(readable_message, 'nasa/harmony-gdal-adapter') + # Call the parent class constructor with the formatted message and category + super().__init__(readable_message, 'podaac/concise') + + # Store the original exception for potential further investigation + self.original_exception = original_exception class ConciseService(BaseHarmonyAdapter): diff --git a/tests/test_concise_exception.py b/tests/test_concise_exception.py new file mode 100644 index 00000000..b3813781 --- /dev/null +++ b/tests/test_concise_exception.py @@ -0,0 +1,74 @@ +import pytest +import traceback +import sys +from podaac.merger.harmony.service import ConciseException + + +def test_exception_message_formatting(): + """ + Test that the ConciseException correctly formats the error message + with file, line, function, and original error details. + """ + try: + # Simulate an error by intentionally causing a division by zero + 1 / 0 + except ZeroDivisionError as original_error: + concise_exception = ConciseException(original_error) + + # Detailed assertions with informative error messages + error_str = str(concise_exception) + error_msg = concise_exception.message + + assert "Error in file" in error_msg, f"Expected file context, got: {error_msg}" + assert "line" in error_msg, f"Expected line number, got: {error_msg}" + assert "in function" in error_msg, f"Expected function context, got: {error_msg}" + assert "division by zero" in error_msg, f"Expected original error message, got: {error_msg}" + assert concise_exception.category == 'podaac/concise' + +def test_exception_traceback_details(): + """ + Verify that the exception captures the correct traceback information. + """ + def inner_function(): + # Another function to add depth to the traceback + 1 / 0 + + try: + inner_function() + except ZeroDivisionError as original_error: + concise_exception = ConciseException(original_error) + + # Extract expected details + tb = traceback.extract_tb(original_error.__traceback__)[-1] + expected_filename = tb.filename + expected_lineno = tb.lineno + expected_funcname = tb.name + + error_msg = concise_exception.message + assert expected_filename in error_msg, f"Filename not found, got: {error_msg}" + assert str(expected_lineno) in error_msg, f"Line number not found, got: {error_msg}" + assert expected_funcname in error_msg, f"Function name not found, got: {error_msg}" + +def test_original_error_type_preservation(): + """ + Ensure that the original error type is preserved in the traceback. + """ + try: + raise ValueError("Test error message") + except ValueError as original_error: + concise_exception = ConciseException(original_error) + + error_msg = concise_exception.message + assert "Test error message" in error_msg, f"Original error message not found, got: {error_msg}" + assert isinstance(concise_exception.original_exception, ValueError) + +def test_module_identifier(): + """ + Verify that the module identifier is set correctly. + """ + try: + raise RuntimeError("Sample error") + except RuntimeError as original_error: + concise_exception = ConciseException(original_error) + + assert concise_exception.category == 'podaac/concise' \ No newline at end of file