diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index eeee118ed7..bbf8568eb9 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -482,8 +482,12 @@ def get_lines_from_file( if has_data_collection_enabled(client_options): context_lines = client_options["data_collection"]["frame_context_lines"] - source = None - if loader is not None and hasattr(loader, "get_source"): + try: + source = linecache.getlines(filename) + except (OSError, IOError): + source = [] + + if not source and loader is not None and hasattr(loader, "get_source"): try: source_str: "Optional[str]" = loader.get_source(module) except (ImportError, IOError): @@ -491,12 +495,6 @@ def get_lines_from_file( if source_str is not None: source = source_str.splitlines() - if source is None: - try: - source = linecache.getlines(filename) - except (OSError, IOError): - return [], None, [] - if not source: return [], None, [] diff --git a/tests/test_utils.py b/tests/test_utils.py index 77def0eb6e..bc7b693526 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1330,6 +1330,47 @@ def fake_getlines(filename): assert result == expected_result +def test_get_lines_from_file_prefers_linecache(tmp_path): + filename = tmp_path / "source.py" + filename.write_text("before\ntarget\nafter\n") + calls = [] + + class Loader: + def get_source(self, module): + calls.append(module) + return "loader source\n" + + result = get_lines_from_file( + str(filename), 1, loader=Loader(), module="source_module" + ) + + assert result == (["before"], "target", ["after"]) + assert calls == [] + + +def test_get_lines_from_file_falls_back_to_loader(tmp_path): + filename = tmp_path / "missing.py" + calls = [] + + class Loader: + def get_source(self, module): + calls.append(module) + return "before\none\ntarget\nafter\n" + + result = get_lines_from_file( + str(filename), 2, loader=Loader(), module="fallback_module" + ) + + assert result == (["before", "one"], "target", ["after"]) + assert calls == ["fallback_module"] + + +def test_get_lines_from_file_without_source(tmp_path): + result = get_lines_from_file(str(tmp_path / "missing.py"), 0) + + assert result == ([], None, []) + + @pytest.mark.parametrize( "options,expected_context_lines", [