Skip to content

Commit 48998df

Browse files
[3.15] gh-153838: Skip non-regular source files in the heatmap exporter (GH-153839) (#158853)
Backport of GH-153839. Co-authored-by: tonghuaroot <tonghuaroot@gmail.com>
1 parent f52d831 commit 48998df

3 files changed

Lines changed: 45 additions & 12 deletions

File tree

‎Lib/profiling/sampling/heatmap_collector.py‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -785,14 +785,14 @@ def _generate_file_html(self, output_path: Path, filename: str,
785785
line_counts: Dict[int, int], self_counts: Dict[int, int],
786786
file_stat: FileStats):
787787
"""Generate HTML for a single source file with heatmap coloring."""
788-
# Read source file
788+
source_lines = [f"# Source file not available: {filename}"]
789789
try:
790-
source_lines = Path(filename).read_text(encoding='utf-8', errors='replace').splitlines()
791-
except (IOError, OSError) as e:
792-
if not (filename.startswith('<') or filename.startswith('[') or
793-
filename in ('~', '...', '.') or len(filename) < 2):
794-
print(f"Warning: Could not read source file {filename}: {e}")
795-
source_lines = [f"# Source file not available: {filename}"]
790+
path = Path(filename)
791+
if path.is_file():
792+
source_lines = path.read_text(
793+
encoding='utf-8', errors='replace').splitlines()
794+
except (IOError, OSError):
795+
pass
796796

797797
# Generate HTML for each line
798798
max_samples = max(line_counts.values()) if line_counts else 1

‎Lib/test/test_profiling/test_heatmap.py‎

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import shutil
55
import tempfile
66
import unittest
7+
from unittest import mock
78
from collections import namedtuple
89
from pathlib import Path
910

@@ -615,13 +616,43 @@ def test_export_file_html_has_line_numbers(self):
615616
html_files = [f for f in os.listdir(output_path)
616617
if f.startswith('file_') and f.endswith('.html')]
617618

618-
if html_files:
619-
with open(os.path.join(output_path, html_files[0]), 'r', encoding='utf-8') as f:
620-
content = f.read()
619+
self.assertEqual(len(html_files), 1)
620+
with open(os.path.join(output_path, html_files[0]), 'r', encoding='utf-8') as f:
621+
content = f.read()
622+
623+
# Should have line-related content
624+
self.assertIn('line-', content)
625+
626+
def test_export_skips_nonexistent_source(self):
627+
self.check_unavailable_source(os.path.join(self.test_dir, 'missing.py'))
621628

622-
# Should have line-related content
623-
self.assertIn('line-', content)
629+
def test_export_skips_directory_source(self):
630+
self.check_unavailable_source(self.test_dir)
624631

632+
@unittest.skipUnless(hasattr(os, 'mkfifo'), 'requires os.mkfifo')
633+
def test_export_skips_fifo_source(self):
634+
filename = os.path.join(self.test_dir, 'source.fifo')
635+
os.mkfifo(filename)
636+
self.check_unavailable_source(filename)
637+
638+
def check_unavailable_source(self, filename):
639+
collector = HeatmapCollector(sample_interval_usec=100)
640+
frames = [(filename, (1, 1, -1, -1), 'f', None)]
641+
collector.process_frames(frames, thread_id=1)
642+
output_path = Path(self.test_dir) / 'unavailable_source'
643+
read_text = Path.read_text
644+
645+
def check_read(path, *args, **kwargs):
646+
self.assertNotEqual(path, Path(filename),
647+
'Non-regular source must not be opened')
648+
return read_text(path, *args, **kwargs)
649+
650+
with (captured_stdout(), captured_stderr(),
651+
mock.patch.object(Path, 'read_text', check_read)):
652+
collector.export(output_path)
653+
content = (output_path / collector.file_index[filename]).read_text(
654+
encoding='utf-8')
655+
self.assertIn('Source file not available', content)
625656

626657
class MockFrameInfo:
627658
"""Mock FrameInfo for testing.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Skip non-regular files in the ``profiling.sampling`` heatmap exporter
2+
instead of reading them unconditionally. Patch by tonghuaroot.

0 commit comments

Comments
 (0)