Skip to content

Commit f976359

Browse files
authored
Pop on unreadable files (#28)
Version 0.4.3: Fixed invalid source code behavior when the source file doesn't exist or couldn't be read (#28)
1 parent 2f9c5b8 commit f976359

File tree

6 files changed

+86
-5
lines changed

6 files changed

+86
-5
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
runs-on: ubuntu-22.04
1212
strategy:
1313
matrix:
14-
python-version: [3.7, 3.8, 3.9, 3.11] # List of Python versions to test against
14+
python-version: [3.7, 3.8, 3.9, 3.11, 3.12, 3.13] # List of Python versions to test against
1515

1616
steps:
1717
- name: Checkout code
@@ -46,7 +46,7 @@ jobs:
4646
- name: Set up Python
4747
uses: actions/setup-python@v4
4848
with:
49-
python-version: 3.11
49+
python-version: 3.13
5050
update-environment: false
5151

5252
- name: Install dependencies

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Version 0.4.3
2+
3+
- Fixed invalid source code behavior when the source file doesn't exist or couldn't be read (#28)
4+
15
# Version 0.4.2
26

37
- Fixed a bug that can mark 2+ threads as a faulting thread (#26),

backtracepython/source_code_handler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def collect(self, report):
4242
if new_max_line > source["maxLine"]:
4343
source["maxLine"] = new_max_line
4444

45-
for source_code_path in source_code:
45+
for source_code_path in list(source_code):
4646
source = source_code[source_code_path]
4747
source_code_content = self.read_source(
4848
source_code_path, source["startLine"] - 1, source["maxLine"]

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
setup(
1313
name="backtracepython",
14-
version="0.4.2",
14+
version="0.4.3",
1515
description="Backtrace.io error reporting tool for Python",
1616
author="Backtrace.io",
1717
author_email="team@backtrace.io",

tests/test_source_code_handler.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import os
2+
3+
from backtracepython.source_code_handler import SourceCodeHandler
4+
5+
6+
def write_file(path, content):
7+
with open(str(path), "w") as f:
8+
f.write(content)
9+
10+
11+
def make_report(source_paths):
12+
"""Build a minimal report whose main thread stack references the given file paths."""
13+
stack = [
14+
{"sourceCode": path, "line": 10, "funcName": "test"} for path in source_paths
15+
]
16+
return {
17+
"mainThread": "main",
18+
"threads": {
19+
"main": {"stack": stack},
20+
},
21+
}
22+
23+
24+
def test_collect_removes_unreadable_sources_without_runtime_error():
25+
"""Reproduces RuntimeError: dictionary changed size during iteration.
26+
27+
When every source file in the stack is unreadable, collect() used to pop
28+
entries from the source_code dict while iterating over it.
29+
"""
30+
handler = SourceCodeHandler(tab_width=4, context_line_count=3)
31+
report = make_report(
32+
[
33+
"/nonexistent/path/a.py",
34+
"/nonexistent/path/b.py",
35+
]
36+
)
37+
38+
# Before the fix this raised:
39+
# RuntimeError: dictionary changed size during iteration
40+
result = handler.collect(report)
41+
42+
assert result["sourceCode"] == {}
43+
44+
45+
def test_collect_keeps_readable_sources(tmp_path):
46+
"""Verify that readable source files are collected normally."""
47+
source_file = tmp_path / "real.py"
48+
write_file(source_file, "foobarbaz")
49+
50+
handler = SourceCodeHandler(tab_width=4, context_line_count=1)
51+
report = make_report([str(source_file)])
52+
53+
result = handler.collect(report)
54+
55+
assert str(source_file) in result["sourceCode"]
56+
assert "text" in result["sourceCode"][str(source_file)]
57+
58+
59+
def test_collect_mixed_readable_and_unreadable(tmp_path):
60+
"""Mix of existing and missing files"""
61+
source_file = tmp_path / "exists.py"
62+
write_file(source_file, "foobarbaz")
63+
64+
handler = SourceCodeHandler(tab_width=4, context_line_count=3)
65+
report = make_report(
66+
[
67+
"/nonexistent/path/missing.py",
68+
str(source_file),
69+
"/another/missing/file.py",
70+
]
71+
)
72+
73+
result = handler.collect(report)
74+
75+
assert str(source_file) in result["sourceCode"]
76+
assert "/nonexistent/path/missing.py" not in result["sourceCode"]
77+
assert "/another/missing/file.py" not in result["sourceCode"]

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[tox]
2-
envlist = py27, py37, py38, py39, py310
2+
envlist = py27, py37, py38, py39, py310, py312, py313
33
skipsdist = True
44

55
[testenv]

0 commit comments

Comments
 (0)