https://github.com/python/cpython/commit/07410da204648efac126a42e7c9ae24031134a08
commit: 07410da204648efac126a42e7c9ae24031134a08
branch: main
author: Pablo Galindo Salgado <[email protected]>
committer: pablogsal <[email protected]>
date: 2025-11-21T00:35:37Z
summary:

gh-141645: Refactor tachyon's live TUI tests to not use private fields (#141806)

files:
A Lib/data.bin
M Lib/profiling/sampling/live_collector/collector.py
M Lib/profiling/sampling/live_collector/widgets.py
M Lib/test/test_profiling/test_sampling_profiler/test_live_collector_core.py
M 
Lib/test/test_profiling/test_sampling_profiler/test_live_collector_interaction.py
M Lib/test/test_profiling/test_sampling_profiler/test_live_collector_ui.py

diff --git a/Lib/data.bin b/Lib/data.bin
new file mode 100644
index 00000000000000..1d1fab72c4aaa4
Binary files /dev/null and b/Lib/data.bin differ
diff --git a/Lib/profiling/sampling/live_collector/collector.py 
b/Lib/profiling/sampling/live_collector/collector.py
index 137657f051ba35..4b69275a2f077f 100644
--- a/Lib/profiling/sampling/live_collector/collector.py
+++ b/Lib/profiling/sampling/live_collector/collector.py
@@ -137,20 +137,20 @@ def __init__(
         self._saved_stderr = None
         self._devnull = None
         self._last_display_update = None
-        self._max_sample_rate = 0  # Track maximum sample rate seen
-        self._successful_samples = 0  # Track samples that captured frames
-        self._failed_samples = 0  # Track samples that failed to capture frames
-        self._display_update_interval = DISPLAY_UPDATE_INTERVAL  # Instance 
variable for display refresh rate
+        self.max_sample_rate = 0  # Track maximum sample rate seen
+        self.successful_samples = 0  # Track samples that captured frames
+        self.failed_samples = 0  # Track samples that failed to capture frames
+        self.display_update_interval = DISPLAY_UPDATE_INTERVAL  # Instance 
variable for display refresh rate
 
         # Thread status statistics (bit flags)
-        self._thread_status_counts = {
+        self.thread_status_counts = {
             "has_gil": 0,
             "on_cpu": 0,
             "gil_requested": 0,
             "unknown": 0,
             "total": 0,  # Total thread count across all samples
         }
-        self._gc_frame_samples = 0  # Track samples with GC frames
+        self.gc_frame_samples = 0  # Track samples with GC frames
 
         # Interactive controls state
         self.paused = False  # Pause UI updates (profiling continues)
@@ -174,10 +174,10 @@ def __init__(
         self._path_prefixes = self._get_common_path_prefixes()
 
         # Widgets (initialized when display is available)
-        self._header_widget = None
-        self._table_widget = None
-        self._footer_widget = None
-        self._help_widget = None
+        self.header_widget = None
+        self.table_widget = None
+        self.footer_widget = None
+        self.help_widget = None
 
         # Color mode
         self._can_colorize = _colorize.can_colorize()
@@ -256,7 +256,7 @@ def _get_common_path_prefixes(self):
 
         return prefixes
 
-    def _simplify_path(self, filepath):
+    def simplify_path(self, filepath):
         """Simplify a file path by removing common prefixes."""
         # Try to match against known prefixes
         for prefix_path in self._path_prefixes:
@@ -268,7 +268,7 @@ def _simplify_path(self, filepath):
         # If no match, return the original path
         return filepath
 
-    def _process_frames(self, frames, thread_id=None):
+    def process_frames(self, frames, thread_id=None):
         """Process a single thread's frame stack.
 
         Args:
@@ -295,7 +295,7 @@ def _process_frames(self, frames, thread_id=None):
             thread_data.result[top_location]["direct_calls"] += 1
 
     def collect_failed_sample(self):
-        self._failed_samples += 1
+        self.failed_samples += 1
         self.total_samples += 1
 
     def collect(self, stack_frames):
@@ -349,7 +349,7 @@ def collect(self, stack_frames):
 
                 frames = getattr(thread_info, "frame_info", None)
                 if frames:
-                    self._process_frames(frames, thread_id=thread_id)
+                    self.process_frames(frames, thread_id=thread_id)
 
                     # Track thread IDs only for threads that actually have 
samples
                     if (
@@ -375,12 +375,12 @@ def collect(self, stack_frames):
 
         # Update cumulative thread status counts
         for key, count in temp_status_counts.items():
-            self._thread_status_counts[key] += count
+            self.thread_status_counts[key] += count
 
         if has_gc_frame:
-            self._gc_frame_samples += 1
+            self.gc_frame_samples += 1
 
-        self._successful_samples += 1
+        self.successful_samples += 1
         self.total_samples += 1
 
         # Handle input on every sample for instant responsiveness
@@ -393,7 +393,7 @@ def collect(self, stack_frames):
             if (
                 self._last_display_update is None
                 or (current_time - self._last_display_update)
-                >= self._display_update_interval
+                >= self.display_update_interval
             ):
                 self._update_display()
                 self._last_display_update = current_time
@@ -401,7 +401,7 @@ def collect(self, stack_frames):
     def _prepare_display_data(self, height):
         """Prepare data for display rendering."""
         elapsed = self.elapsed_time
-        stats_list = self._build_stats_list()
+        stats_list = self.build_stats_list()
 
         # Calculate available space for stats
         # Add extra lines for finished banner when in finished state
@@ -422,15 +422,15 @@ def _prepare_display_data(self, height):
 
     def _initialize_widgets(self, colors):
         """Initialize widgets with display and colors."""
-        if self._header_widget is None:
+        if self.header_widget is None:
             # Initialize trend tracker with colors
             if self._trend_tracker is None:
                 self._trend_tracker = TrendTracker(colors, enabled=True)
 
-            self._header_widget = HeaderWidget(self.display, colors, self)
-            self._table_widget = TableWidget(self.display, colors, self)
-            self._footer_widget = FooterWidget(self.display, colors, self)
-            self._help_widget = HelpWidget(self.display, colors)
+            self.header_widget = HeaderWidget(self.display, colors, self)
+            self.table_widget = TableWidget(self.display, colors, self)
+            self.footer_widget = FooterWidget(self.display, colors, self)
+            self.help_widget = HelpWidget(self.display, colors)
 
     def _render_display_sections(
         self, height, width, elapsed, stats_list, colors
@@ -442,12 +442,12 @@ def _render_display_sections(
             self._initialize_widgets(colors)
 
             # Render header
-            line = self._header_widget.render(
+            line = self.header_widget.render(
                 line, width, elapsed=elapsed, stats_list=stats_list
             )
 
             # Render table
-            line = self._table_widget.render(
+            line = self.table_widget.render(
                 line, width, height=height, stats_list=stats_list
             )
 
@@ -473,7 +473,7 @@ def _update_display(self):
 
             # Show help screen if requested
             if self.show_help:
-                self._help_widget.render(0, width, height=height)
+                self.help_widget.render(0, width, height=height)
                 self.display.refresh()
                 return
 
@@ -486,11 +486,11 @@ def _update_display(self):
             )
 
             # Footer
-            self._footer_widget.render(height - 2, width)
+            self.footer_widget.render(height - 2, width)
 
             # Show filter input prompt if in filter input mode
             if self.filter_input_mode:
-                self._footer_widget.render_filter_input_prompt(
+                self.footer_widget.render_filter_input_prompt(
                     height - 1, width
                 )
 
@@ -616,7 +616,7 @@ def _setup_colors(self):
             "trend_stable": A_NORMAL,
         }
 
-    def _build_stats_list(self):
+    def build_stats_list(self):
         """Build and sort the statistics list."""
         stats_list = []
         result_source = self._get_current_result_source()
@@ -707,17 +707,17 @@ def reset_stats(self):
         self.view_mode = "ALL"
         self.current_thread_index = 0
         self.total_samples = 0
-        self._successful_samples = 0
-        self._failed_samples = 0
-        self._max_sample_rate = 0
-        self._thread_status_counts = {
+        self.successful_samples = 0
+        self.failed_samples = 0
+        self.max_sample_rate = 0
+        self.thread_status_counts = {
             "has_gil": 0,
             "on_cpu": 0,
             "gil_requested": 0,
             "unknown": 0,
             "total": 0,
         }
-        self._gc_frame_samples = 0
+        self.gc_frame_samples = 0
         # Clear trend tracking
         if self._trend_tracker is not None:
             self._trend_tracker.clear()
@@ -886,14 +886,14 @@ def _handle_input(self):
 
         elif ch == ord("+") or ch == ord("="):
             # Decrease update interval (faster refresh)
-            self._display_update_interval = max(
-                0.05, self._display_update_interval - 0.05
+            self.display_update_interval = max(
+                0.05, self.display_update_interval - 0.05
             )  # Min 20Hz
 
         elif ch == ord("-") or ch == ord("_"):
             # Increase update interval (slower refresh)
-            self._display_update_interval = min(
-                1.0, self._display_update_interval + 0.05
+            self.display_update_interval = min(
+                1.0, self.display_update_interval + 0.05
             )  # Max 1Hz
 
         elif ch == ord("c") or ch == ord("C"):
diff --git a/Lib/profiling/sampling/live_collector/widgets.py 
b/Lib/profiling/sampling/live_collector/widgets.py
index ffc566a8a2a56e..2af8caa2c2f6d9 100644
--- a/Lib/profiling/sampling/live_collector/widgets.py
+++ b/Lib/profiling/sampling/live_collector/widgets.py
@@ -180,7 +180,7 @@ def draw_header_info(self, line, width, elapsed):
 
         # Calculate display refresh rate
         refresh_hz = (
-            1.0 / self.collector._display_update_interval if 
self.collector._display_update_interval > 0 else 0
+            1.0 / self.collector.display_update_interval if 
self.collector.display_update_interval > 0 else 0
         )
 
         # Get current view mode and thread display
@@ -248,8 +248,8 @@ def draw_sample_stats(self, line, width, elapsed):
         )
 
         # Update max sample rate
-        if sample_rate > self.collector._max_sample_rate:
-            self.collector._max_sample_rate = sample_rate
+        if sample_rate > self.collector.max_sample_rate:
+            self.collector.max_sample_rate = sample_rate
 
         col = 0
         self.add_str(line, col, "Samples: ", curses.A_BOLD)
@@ -308,11 +308,11 @@ def draw_sample_stats(self, line, width, elapsed):
     def draw_efficiency_bar(self, line, width):
         """Draw sample efficiency bar showing success/failure rates."""
         success_pct = (
-            self.collector._successful_samples
+            self.collector.successful_samples
             / max(1, self.collector.total_samples)
         ) * 100
         failed_pct = (
-            self.collector._failed_samples
+            self.collector.failed_samples
             / max(1, self.collector.total_samples)
         ) * 100
 
@@ -327,7 +327,7 @@ def draw_efficiency_bar(self, line, width):
             bar_width = min(MAX_EFFICIENCY_BAR_WIDTH, available_width)
             success_fill = int(
                 (
-                    self.collector._successful_samples
+                    self.collector.successful_samples
                     / max(1, self.collector.total_samples)
                 )
                 * bar_width
@@ -381,7 +381,7 @@ def draw_thread_status(self, line, width):
         """Draw thread status statistics and GC information."""
         # Get status counts for current view mode
         thread_data = self.collector._get_current_thread_data()
-        status_counts = thread_data.as_status_dict() if thread_data else 
self.collector._thread_status_counts
+        status_counts = thread_data.as_status_dict() if thread_data else 
self.collector.thread_status_counts
 
         # Calculate percentages
         total_threads = max(1, status_counts["total"])
@@ -395,7 +395,7 @@ def draw_thread_status(self, line, width):
             pct_gc = (thread_data.gc_frame_samples / total_samples) * 100
         else:
             total_samples = max(1, self.collector.total_samples)
-            pct_gc = (self.collector._gc_frame_samples / total_samples) * 100
+            pct_gc = (self.collector.gc_frame_samples / total_samples) * 100
 
         col = 0
         self.add_str(line, col, "Threads:   ", curses.A_BOLD)
@@ -809,7 +809,7 @@ def get_trend_color(column_name):
 
                 # File:line column
                 if col < width - 10:
-                    simplified_path = self.collector._simplify_path(filename)
+                    simplified_path = self.collector.simplify_path(filename)
                     file_line = f"{simplified_path}:{lineno}"
                     remaining_width = width - col - 1
                     self.add_str(
diff --git 
a/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_core.py 
b/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_core.py
index bfdc9d830cf14c..04e6cd2f1fcb8b 100644
--- a/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_core.py
+++ b/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_core.py
@@ -36,7 +36,7 @@ def test_simplify_stdlib_path(self):
         if os_file:
             stdlib_dir = os.path.dirname(os.path.abspath(os_file))
             test_path = os.path.join(stdlib_dir, "json", "decoder.py")
-            simplified = collector._simplify_path(test_path)
+            simplified = collector.simplify_path(test_path)
             # Should remove the stdlib prefix
             self.assertNotIn(stdlib_dir, simplified)
             self.assertIn("json", simplified)
@@ -45,7 +45,7 @@ def test_simplify_unknown_path(self):
         """Test that unknown paths are returned unchanged."""
         collector = LiveStatsCollector(1000)
         test_path = "/some/unknown/path/file.py"
-        simplified = collector._simplify_path(test_path)
+        simplified = collector.simplify_path(test_path)
         self.assertEqual(simplified, test_path)
 
 
@@ -56,7 +56,7 @@ def test_process_single_frame(self):
         """Test processing a single frame."""
         collector = LiveStatsCollector(1000)
         frames = [MockFrameInfo("test.py", 10, "test_func")]
-        collector._process_frames(frames)
+        collector.process_frames(frames)
 
         location = ("test.py", 10, "test_func")
         self.assertEqual(collector.result[location]["direct_calls"], 1)
@@ -70,7 +70,7 @@ def test_process_multiple_frames(self):
             MockFrameInfo("test.py", 20, "middle_func"),
             MockFrameInfo("test.py", 30, "outer_func"),
         ]
-        collector._process_frames(frames)
+        collector.process_frames(frames)
 
         # Top frame (inner_func) should have both direct and cumulative
         inner_loc = ("test.py", 10, "inner_func")
@@ -89,7 +89,7 @@ def test_process_multiple_frames(self):
     def test_process_empty_frames(self):
         """Test processing empty frames list."""
         collector = LiveStatsCollector(1000)
-        collector._process_frames([])
+        collector.process_frames([])
         # Should not raise an error and result should remain empty
         self.assertEqual(len(collector.result), 0)
 
@@ -98,9 +98,9 @@ def test_process_frames_accumulation(self):
         collector = LiveStatsCollector(1000)
         frames = [MockFrameInfo("test.py", 10, "test_func")]
 
-        collector._process_frames(frames)
-        collector._process_frames(frames)
-        collector._process_frames(frames)
+        collector.process_frames(frames)
+        collector.process_frames(frames)
+        collector.process_frames(frames)
 
         location = ("test.py", 10, "test_func")
         self.assertEqual(collector.result[location]["direct_calls"], 3)
@@ -112,7 +112,7 @@ def test_process_frames_with_thread_id(self):
         frames = [MockFrameInfo("test.py", 10, "test_func")]
 
         # Process frames with thread_id
-        collector._process_frames(frames, thread_id=123)
+        collector.process_frames(frames, thread_id=123)
 
         # Check aggregated result
         location = ("test.py", 10, "test_func")
@@ -135,8 +135,8 @@ def test_process_frames_multiple_threads(self):
         frames2 = [MockFrameInfo("test.py", 20, "other_func")]
 
         # Process frames from different threads
-        collector._process_frames(frames1, thread_id=123)
-        collector._process_frames(frames2, thread_id=456)
+        collector.process_frames(frames1, thread_id=123)
+        collector.process_frames(frames2, thread_id=456)
 
         # Check that both threads have their own data
         self.assertIn(123, collector.per_thread_data)
@@ -199,8 +199,8 @@ def test_collect_with_frames(self):
 
         location = ("test.py", 10, "test_func")
         self.assertEqual(collector.result[location]["direct_calls"], 1)
-        self.assertEqual(collector._successful_samples, 1)
-        self.assertEqual(collector._failed_samples, 0)
+        self.assertEqual(collector.successful_samples, 1)
+        self.assertEqual(collector.failed_samples, 0)
 
     def test_collect_with_empty_frames(self):
         """Test collect with empty frames."""
@@ -212,8 +212,8 @@ def test_collect_with_empty_frames(self):
         collector.collect(stack_frames)
 
         # Empty frames still count as successful since collect() was called 
successfully
-        self.assertEqual(collector._successful_samples, 1)
-        self.assertEqual(collector._failed_samples, 0)
+        self.assertEqual(collector.successful_samples, 1)
+        self.assertEqual(collector.failed_samples, 0)
 
     def test_collect_skip_idle_threads(self):
         """Test that idle threads are skipped when skip_idle=True."""
@@ -284,7 +284,7 @@ def setUp(self):
 
     def test_build_stats_list(self):
         """Test that stats list is built correctly."""
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
         self.assertEqual(len(stats_list), 3)
 
         # Check that all expected keys are present
@@ -298,7 +298,7 @@ def test_build_stats_list(self):
     def test_sort_by_nsamples(self):
         """Test sorting by number of samples."""
         self.collector.sort_by = "nsamples"
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
 
         # Should be sorted by direct_calls descending
         self.assertEqual(stats_list[0]["func"][2], "func1")  # 100 samples
@@ -308,7 +308,7 @@ def test_sort_by_nsamples(self):
     def test_sort_by_tottime(self):
         """Test sorting by total time."""
         self.collector.sort_by = "tottime"
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
 
         # Should be sorted by total_time descending
         # total_time = direct_calls * sample_interval_sec
@@ -319,7 +319,7 @@ def test_sort_by_tottime(self):
     def test_sort_by_cumtime(self):
         """Test sorting by cumulative time."""
         self.collector.sort_by = "cumtime"
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
 
         # Should be sorted by cumulative_time descending
         self.assertEqual(stats_list[0]["func"][2], "func2")  # 200 cumulative
@@ -329,7 +329,7 @@ def test_sort_by_cumtime(self):
     def test_sort_by_sample_pct(self):
         """Test sorting by sample percentage."""
         self.collector.sort_by = "sample_pct"
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
 
         # Should be sorted by percentage of direct_calls
         self.assertEqual(stats_list[0]["func"][2], "func1")  # 33.3%
@@ -339,7 +339,7 @@ def test_sort_by_sample_pct(self):
     def test_sort_by_cumul_pct(self):
         """Test sorting by cumulative percentage."""
         self.collector.sort_by = "cumul_pct"
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
 
         # Should be sorted by percentage of cumulative_calls
         self.assertEqual(stats_list[0]["func"][2], "func2")  # 66.7%
@@ -438,14 +438,14 @@ def test_format_uptime_seconds(self):
         collector = LiveStatsCollector(1000, display=MockDisplay())
         colors = collector._setup_colors()
         collector._initialize_widgets(colors)
-        self.assertEqual(collector._header_widget.format_uptime(45), "0m45s")
+        self.assertEqual(collector.header_widget.format_uptime(45), "0m45s")
 
     def test_format_uptime_minutes(self):
         """Test uptime formatting for minutes."""
         collector = LiveStatsCollector(1000, display=MockDisplay())
         colors = collector._setup_colors()
         collector._initialize_widgets(colors)
-        self.assertEqual(collector._header_widget.format_uptime(125), "2m05s")
+        self.assertEqual(collector.header_widget.format_uptime(125), "2m05s")
 
     def test_format_uptime_hours(self):
         """Test uptime formatting for hours."""
@@ -453,7 +453,7 @@ def test_format_uptime_hours(self):
         colors = collector._setup_colors()
         collector._initialize_widgets(colors)
         self.assertEqual(
-            collector._header_widget.format_uptime(3661), "1h01m01s"
+            collector.header_widget.format_uptime(3661), "1h01m01s"
         )
 
     def test_format_uptime_large_values(self):
@@ -462,7 +462,7 @@ def test_format_uptime_large_values(self):
         colors = collector._setup_colors()
         collector._initialize_widgets(colors)
         self.assertEqual(
-            collector._header_widget.format_uptime(86400), "24h00m00s"
+            collector.header_widget.format_uptime(86400), "24h00m00s"
         )
 
     def test_format_uptime_zero(self):
@@ -470,7 +470,7 @@ def test_format_uptime_zero(self):
         collector = LiveStatsCollector(1000, display=MockDisplay())
         colors = collector._setup_colors()
         collector._initialize_widgets(colors)
-        self.assertEqual(collector._header_widget.format_uptime(0), "0m00s")
+        self.assertEqual(collector.header_widget.format_uptime(0), "0m00s")
 
 
 if __name__ == "__main__":
diff --git 
a/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_interaction.py
 
b/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_interaction.py
index 3c226987323cc9..388f462cf21b3d 100644
--- 
a/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_interaction.py
+++ 
b/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_interaction.py
@@ -35,7 +35,7 @@ def setUp(self):
         )
         self.collector.start_time = time.perf_counter()
         # Set a consistent display update interval for tests
-        self.collector._display_update_interval = 0.1
+        self.collector.display_update_interval = 0.1
 
     def tearDown(self):
         """Clean up after test."""
@@ -92,8 +92,8 @@ def test_reset_stats(self):
         """Test reset statistics functionality."""
         # Add some stats
         self.collector.total_samples = 100
-        self.collector._successful_samples = 90
-        self.collector._failed_samples = 10
+        self.collector.successful_samples = 90
+        self.collector.failed_samples = 10
         self.collector.result[("test.py", 1, "func")] = {
             "direct_calls": 50,
             "cumulative_calls": 75,
@@ -104,51 +104,51 @@ def test_reset_stats(self):
         self.collector.reset_stats()
 
         self.assertEqual(self.collector.total_samples, 0)
-        self.assertEqual(self.collector._successful_samples, 0)
-        self.assertEqual(self.collector._failed_samples, 0)
+        self.assertEqual(self.collector.successful_samples, 0)
+        self.assertEqual(self.collector.failed_samples, 0)
         self.assertEqual(len(self.collector.result), 0)
 
     def test_increase_refresh_rate(self):
         """Test increasing refresh rate (faster updates)."""
-        initial_interval = self.collector._display_update_interval
+        initial_interval = self.collector.display_update_interval
 
         # Simulate '+' key press (faster = smaller interval)
         self.display.simulate_input(ord("+"))
         self.collector._handle_input()
 
-        self.assertLess(self.collector._display_update_interval, 
initial_interval)
+        self.assertLess(self.collector.display_update_interval, 
initial_interval)
 
     def test_decrease_refresh_rate(self):
         """Test decreasing refresh rate (slower updates)."""
-        initial_interval = self.collector._display_update_interval
+        initial_interval = self.collector.display_update_interval
 
         # Simulate '-' key press (slower = larger interval)
         self.display.simulate_input(ord("-"))
         self.collector._handle_input()
 
-        self.assertGreater(self.collector._display_update_interval, 
initial_interval)
+        self.assertGreater(self.collector.display_update_interval, 
initial_interval)
 
     def test_refresh_rate_minimum(self):
         """Test that refresh rate has a minimum (max speed)."""
-        self.collector._display_update_interval = 0.05  # Set to minimum
+        self.collector.display_update_interval = 0.05  # Set to minimum
 
         # Try to go faster
         self.display.simulate_input(ord("+"))
         self.collector._handle_input()
 
         # Should stay at minimum
-        self.assertEqual(self.collector._display_update_interval, 0.05)
+        self.assertEqual(self.collector.display_update_interval, 0.05)
 
     def test_refresh_rate_maximum(self):
         """Test that refresh rate has a maximum (min speed)."""
-        self.collector._display_update_interval = 1.0  # Set to maximum
+        self.collector.display_update_interval = 1.0  # Set to maximum
 
         # Try to go slower
         self.display.simulate_input(ord("-"))
         self.collector._handle_input()
 
         # Should stay at maximum
-        self.assertEqual(self.collector._display_update_interval, 1.0)
+        self.assertEqual(self.collector.display_update_interval, 1.0)
 
     def test_help_toggle(self):
         """Test help screen toggle."""
@@ -276,23 +276,23 @@ def test_filter_clear_uppercase(self):
 
     def test_increase_refresh_rate_with_equals(self):
         """Test increasing refresh rate with '=' key."""
-        initial_interval = self.collector._display_update_interval
+        initial_interval = self.collector.display_update_interval
 
         # Simulate '=' key press (alternative to '+')
         self.display.simulate_input(ord("="))
         self.collector._handle_input()
 
-        self.assertLess(self.collector._display_update_interval, 
initial_interval)
+        self.assertLess(self.collector.display_update_interval, 
initial_interval)
 
     def test_decrease_refresh_rate_with_underscore(self):
         """Test decreasing refresh rate with '_' key."""
-        initial_interval = self.collector._display_update_interval
+        initial_interval = self.collector.display_update_interval
 
         # Simulate '_' key press (alternative to '-')
         self.display.simulate_input(ord("_"))
         self.collector._handle_input()
 
-        self.assertGreater(self.collector._display_update_interval, 
initial_interval)
+        self.assertGreater(self.collector.display_update_interval, 
initial_interval)
 
     def test_finished_state_displays_banner(self):
         """Test that finished state shows prominent banner."""
@@ -431,7 +431,7 @@ def test_filter_by_filename(self):
         """Test filtering by filename pattern."""
         self.collector.filter_pattern = "models"
 
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
 
         # Only models.py should be included
         self.assertEqual(len(stats_list), 1)
@@ -441,7 +441,7 @@ def test_filter_by_function_name(self):
         """Test filtering by function name."""
         self.collector.filter_pattern = "render"
 
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
 
         self.assertEqual(len(stats_list), 1)
         self.assertEqual(stats_list[0]["func"][2], "render")
@@ -450,7 +450,7 @@ def test_filter_case_insensitive(self):
         """Test that filtering is case-insensitive."""
         self.collector.filter_pattern = "MODELS"
 
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
 
         # Should still match models.py
         self.assertEqual(len(stats_list), 1)
@@ -459,7 +459,7 @@ def test_filter_substring_matching(self):
         """Test substring filtering."""
         self.collector.filter_pattern = "app/"
 
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
 
         # Should match both app files
         self.assertEqual(len(stats_list), 2)
@@ -468,7 +468,7 @@ def test_no_filter(self):
         """Test with no filter applied."""
         self.collector.filter_pattern = None
 
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
 
         # All items should be included
         self.assertEqual(len(stats_list), 3)
@@ -477,7 +477,7 @@ def test_filter_partial_function_name(self):
         """Test filtering by partial function name."""
         self.collector.filter_pattern = "save"
 
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
 
         self.assertEqual(len(stats_list), 1)
         self.assertEqual(stats_list[0]["func"][2], "save")
@@ -486,7 +486,7 @@ def test_filter_combined_filename_funcname(self):
         """Test filtering matches filename:funcname pattern."""
         self.collector.filter_pattern = "views.py:render"
 
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
 
         # Should match the combined pattern
         self.assertEqual(len(stats_list), 1)
@@ -496,7 +496,7 @@ def test_filter_no_matches(self):
         """Test filter that matches nothing."""
         self.collector.filter_pattern = "nonexistent"
 
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
 
         self.assertEqual(len(stats_list), 0)
 
@@ -822,7 +822,7 @@ def test_arrow_keys_switch_to_per_thread_mode(self):
 
     def test_stats_list_in_all_mode(self):
         """Test that stats list uses aggregated data in ALL mode."""
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
 
         # Should have all 3 functions
         self.assertEqual(len(stats_list), 3)
@@ -835,7 +835,7 @@ def test_stats_list_in_per_thread_mode(self):
         self.collector.view_mode = "PER_THREAD"
         self.collector.current_thread_index = 0  # First thread (111)
 
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
 
         # Should only have func1 from thread 111
         self.assertEqual(len(stats_list), 1)
@@ -847,19 +847,19 @@ def test_stats_list_switches_with_thread_navigation(self):
 
         # Thread 0 (111) -> func1
         self.collector.current_thread_index = 0
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
         self.assertEqual(len(stats_list), 1)
         self.assertEqual(stats_list[0]["func"][2], "func1")
 
         # Thread 1 (222) -> func2
         self.collector.current_thread_index = 1
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
         self.assertEqual(len(stats_list), 1)
         self.assertEqual(stats_list[0]["func"][2], "func2")
 
         # Thread 2 (333) -> func3
         self.collector.current_thread_index = 2
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
         self.assertEqual(len(stats_list), 1)
         self.assertEqual(stats_list[0]["func"][2], "func3")
 
@@ -1036,8 +1036,8 @@ def 
test_display_uses_per_thread_stats_in_per_thread_mode(self):
 
         # In ALL mode, should show mixed stats (50% on GIL, 50% off GIL)
         self.assertEqual(collector.view_mode, "ALL")
-        total_has_gil = collector._thread_status_counts["has_gil"]
-        total_threads = collector._thread_status_counts["total"]
+        total_has_gil = collector.thread_status_counts["has_gil"]
+        total_threads = collector.thread_status_counts["total"]
         self.assertEqual(total_has_gil, 10)  # Only thread 111 has GIL
         self.assertEqual(total_threads, 20)  # 10 samples * 2 threads
 
@@ -1082,7 +1082,7 @@ def 
test_display_uses_per_thread_gc_stats_in_per_thread_mode(self):
 
         # Check aggregated GC stats (ALL mode)
         # 2 GC samples out of 10 total = 20%
-        self.assertEqual(collector._gc_frame_samples, 2)
+        self.assertEqual(collector.gc_frame_samples, 2)
         self.assertEqual(collector.total_samples, 5)  # 5 collect() calls
 
         # Check per-thread GC stats
diff --git 
a/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_ui.py 
b/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_ui.py
index 5c54022356b79c..b5a387fa3a3a71 100644
--- a/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_ui.py
+++ b/Lib/test/test_profiling/test_sampling_profiler/test_live_collector_ui.py
@@ -68,20 +68,20 @@ def test_handle_input_sort_cycle(self):
     def test_draw_methods_with_mock_display(self):
         """Test that draw methods write to mock display."""
         self.collector.total_samples = 500
-        self.collector._successful_samples = 450
-        self.collector._failed_samples = 50
+        self.collector.successful_samples = 450
+        self.collector.failed_samples = 50
 
         colors = self.collector._setup_colors()
         self.collector._initialize_widgets(colors)
 
         # Test individual widget methods
-        line = self.collector._header_widget.draw_header_info(0, 160, 100.5)
+        line = self.collector.header_widget.draw_header_info(0, 160, 100.5)
         self.assertEqual(line, 2)  # Title + header info line
         self.assertGreater(len(self.mock_display.buffer), 0)
 
         # Clear buffer and test next method
         self.mock_display.buffer.clear()
-        line = self.collector._header_widget.draw_sample_stats(0, 160, 10.0)
+        line = self.collector.header_widget.draw_sample_stats(0, 160, 10.0)
         self.assertEqual(line, 1)
         self.assertGreater(len(self.mock_display.buffer), 0)
 
@@ -100,8 +100,8 @@ def test_full_display_rendering_with_data(self):
         """Test complete display rendering with realistic data."""
         # Add multiple functions with different call counts
         self.collector.total_samples = 1000
-        self.collector._successful_samples = 950
-        self.collector._failed_samples = 50
+        self.collector.successful_samples = 950
+        self.collector.failed_samples = 50
 
         self.collector.result[("app.py", 10, "main")] = {
             "direct_calls": 100,
@@ -135,12 +135,12 @@ def test_full_display_rendering_with_data(self):
     def test_efficiency_bar_visualization(self):
         """Test that efficiency bar shows correct proportions."""
         self.collector.total_samples = 100
-        self.collector._successful_samples = 75
-        self.collector._failed_samples = 25
+        self.collector.successful_samples = 75
+        self.collector.failed_samples = 25
 
         colors = self.collector._setup_colors()
         self.collector._initialize_widgets(colors)
-        self.collector._header_widget.draw_efficiency_bar(0, 160)
+        self.collector.header_widget.draw_efficiency_bar(0, 160)
 
         # Check that something was drawn to the display
         self.assertGreater(len(self.mock_display.buffer), 0)
@@ -170,7 +170,7 @@ def test_stats_display_with_different_sort_modes(self):
             self.mock_display.buffer.clear()
             self.collector.sort_by = sort_mode
 
-            stats_list = self.collector._build_stats_list()
+            stats_list = self.collector.build_stats_list()
             self.assertEqual(len(stats_list), 2)
 
             # Verify sorting worked (func_b should be first for most modes)
@@ -186,7 +186,7 @@ def test_narrow_terminal_column_hiding(self):
         colors = collector._setup_colors()
         collector._initialize_widgets(colors)
         line, show_sample_pct, show_tottime, show_cumul_pct, show_cumtime = (
-            collector._table_widget.draw_column_headers(0, 70)
+            collector.table_widget.draw_column_headers(0, 70)
         )
 
         # On narrow terminal, some columns should be hidden
@@ -204,7 +204,7 @@ def test_very_narrow_terminal_minimal_columns(self):
         colors = collector._setup_colors()
         collector._initialize_widgets(colors)
         line, show_sample_pct, show_tottime, show_cumul_pct, show_cumtime = (
-            collector._table_widget.draw_column_headers(0, 60)
+            collector.table_widget.draw_column_headers(0, 60)
         )
 
         # Very narrow should hide even more columns
@@ -252,9 +252,9 @@ def test_top_functions_display(self):
 
         colors = self.collector._setup_colors()
         self.collector._initialize_widgets(colors)
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
 
-        self.collector._header_widget.draw_top_functions(0, 160, stats_list)
+        self.collector.header_widget.draw_top_functions(0, 160, stats_list)
 
         # Top functions section should have written something
         self.assertGreater(len(self.mock_display.buffer), 0)
@@ -334,7 +334,7 @@ def test_add_str_with_mock_display(self):
         colors = collector._setup_colors()
         collector._initialize_widgets(colors)
 
-        collector._header_widget.add_str(5, 10, "Test", 0)
+        collector.header_widget.add_str(5, 10, "Test", 0)
         # Verify it was added to the buffer
         self.assertIn((5, 10), mock_display.buffer)
 
@@ -444,7 +444,7 @@ def test_draw_header_info(self):
         }
         self.collector._initialize_widgets(colors)
 
-        line = self.collector._header_widget.draw_header_info(0, 160, 100.5)
+        line = self.collector.header_widget.draw_header_info(0, 160, 100.5)
         self.assertEqual(line, 2)  # Title + header info line
 
     def test_draw_sample_stats(self):
@@ -453,9 +453,9 @@ def test_draw_sample_stats(self):
         colors = {"cyan": curses.A_BOLD, "green": curses.A_BOLD}
         self.collector._initialize_widgets(colors)
 
-        line = self.collector._header_widget.draw_sample_stats(0, 160, 10.0)
+        line = self.collector.header_widget.draw_sample_stats(0, 160, 10.0)
         self.assertEqual(line, 1)
-        self.assertGreater(self.collector._max_sample_rate, 0)
+        self.assertGreater(self.collector.max_sample_rate, 0)
 
     def test_progress_bar_uses_target_rate(self):
         """Test that progress bar uses target rate instead of max rate."""
@@ -465,7 +465,7 @@ def test_progress_bar_uses_target_rate(self):
         )  # 10ms = 100Hz target
         collector.start_time = time.perf_counter()
         collector.total_samples = 500
-        collector._max_sample_rate = (
+        collector.max_sample_rate = (
             150  # Higher than target to test we don't use this
         )
 
@@ -477,7 +477,7 @@ def test_progress_bar_uses_target_rate(self):
 
         # Draw sample stats with a known elapsed time that gives us a specific 
sample rate
         elapsed = 10.0  # 500 samples in 10 seconds = 50 samples/second
-        line = collector._header_widget.draw_sample_stats(0, 160, elapsed)
+        line = collector.header_widget.draw_sample_stats(0, 160, elapsed)
 
         # Verify display was updated
         self.assertEqual(line, 1)
@@ -543,7 +543,7 @@ def test_progress_bar_different_intervals(self):
                 collector.display.buffer.clear()
 
                 # Draw with 1 second elapsed time (gives us current rate of 
100Hz)
-                collector._header_widget.draw_sample_stats(0, 160, 1.0)
+                collector.header_widget.draw_sample_stats(0, 160, 1.0)
 
                 # Check that the current/target format appears in the display 
with proper units
                 found_current_target_format = False
@@ -564,13 +564,13 @@ def test_progress_bar_different_intervals(self):
 
     def test_draw_efficiency_bar(self):
         """Test drawing efficiency bar."""
-        self.collector._successful_samples = 900
-        self.collector._failed_samples = 100
+        self.collector.successful_samples = 900
+        self.collector.failed_samples = 100
         self.collector.total_samples = 1000
         colors = {"green": curses.A_BOLD, "red": curses.A_BOLD}
         self.collector._initialize_widgets(colors)
 
-        line = self.collector._header_widget.draw_efficiency_bar(0, 160)
+        line = self.collector.header_widget.draw_efficiency_bar(0, 160)
         self.assertEqual(line, 1)
 
     def test_draw_function_stats(self):
@@ -586,7 +586,7 @@ def test_draw_function_stats(self):
             "total_rec_calls": 0,
         }
 
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
         colors = {
             "cyan": curses.A_BOLD,
             "green": curses.A_BOLD,
@@ -595,7 +595,7 @@ def test_draw_function_stats(self):
         }
         self.collector._initialize_widgets(colors)
 
-        line = self.collector._header_widget.draw_function_stats(
+        line = self.collector.header_widget.draw_function_stats(
             0, 160, stats_list
         )
         self.assertEqual(line, 1)
@@ -609,7 +609,7 @@ def test_draw_top_functions(self):
             "total_rec_calls": 0,
         }
 
-        stats_list = self.collector._build_stats_list()
+        stats_list = self.collector.build_stats_list()
         colors = {
             "red": curses.A_BOLD,
             "yellow": curses.A_BOLD,
@@ -617,7 +617,7 @@ def test_draw_top_functions(self):
         }
         self.collector._initialize_widgets(colors)
 
-        line = self.collector._header_widget.draw_top_functions(
+        line = self.collector.header_widget.draw_top_functions(
             0, 160, stats_list
         )
         self.assertEqual(line, 1)
@@ -636,7 +636,7 @@ def test_draw_column_headers(self):
             show_tottime,
             show_cumul_pct,
             show_cumtime,
-        ) = self.collector._table_widget.draw_column_headers(0, 160)
+        ) = self.collector.table_widget.draw_column_headers(0, 160)
         self.assertEqual(line, 1)
         self.assertTrue(show_sample_pct)
         self.assertTrue(show_tottime)
@@ -657,7 +657,7 @@ def test_draw_column_headers_narrow_terminal(self):
             show_tottime,
             show_cumul_pct,
             show_cumtime,
-        ) = self.collector._table_widget.draw_column_headers(0, 70)
+        ) = self.collector.table_widget.draw_column_headers(0, 70)
         self.assertEqual(line, 1)
         # Some columns should be hidden on narrow terminal
         self.assertFalse(show_cumul_pct)
@@ -666,7 +666,7 @@ def test_draw_footer(self):
         """Test drawing footer."""
         colors = self.collector._setup_colors()
         self.collector._initialize_widgets(colors)
-        self.collector._footer_widget.render(38, 160)
+        self.collector.footer_widget.render(38, 160)
         # Should have written some content to the display buffer
         self.assertGreater(len(self.mock_display.buffer), 0)
 
@@ -674,7 +674,7 @@ def test_draw_progress_bar(self):
         """Test progress bar drawing."""
         colors = self.collector._setup_colors()
         self.collector._initialize_widgets(colors)
-        bar, length = self.collector._header_widget.progress_bar.render_bar(
+        bar, length = self.collector.header_widget.progress_bar.render_bar(
             50, 100, 30
         )
 
@@ -699,7 +699,7 @@ def test_very_long_function_name(self):
             "total_rec_calls": 0,
         }
 
-        stats_list = collector._build_stats_list()
+        stats_list = collector.build_stats_list()
         self.assertEqual(len(stats_list), 1)
         self.assertEqual(stats_list[0]["func"][2], long_name)
 
@@ -729,8 +729,8 @@ def test_update_display_terminal_too_small(self):
     def test_update_display_normal(self):
         """Test normal update_display operation."""
         self.collector.total_samples = 100
-        self.collector._successful_samples = 90
-        self.collector._failed_samples = 10
+        self.collector.successful_samples = 90
+        self.collector.failed_samples = 10
         self.collector.result[("test.py", 10, "func")] = {
             "direct_calls": 50,
             "cumulative_calls": 75,

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to