From: Pablo Galindo Salgado Date: Fri, 21 Nov 2025 00:35:37 +0000 (+0000) Subject: gh-141645: Refactor tachyon's live TUI tests to not use private fields (#141806) X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=07410da204648efac126a42e7c9ae24031134a08;p=thirdparty%2FPython%2Fcpython.git gh-141645: Refactor tachyon's live TUI tests to not use private fields (#141806) --- diff --git a/Lib/data.bin b/Lib/data.bin new file mode 100644 index 000000000000..1d1fab72c4aa 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 137657f051ba..4b69275a2f07 100644 --- a/Lib/profiling/sampling/live_collector/collector.py +++ b/Lib/profiling/sampling/live_collector/collector.py @@ -137,20 +137,20 @@ class LiveStatsCollector(Collector): 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 @@ class LiveStatsCollector(Collector): 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 @@ class LiveStatsCollector(Collector): 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 @@ class LiveStatsCollector(Collector): # 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 @@ class LiveStatsCollector(Collector): 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 @@ class LiveStatsCollector(Collector): 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 @@ class LiveStatsCollector(Collector): # 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 @@ class LiveStatsCollector(Collector): 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 @@ class LiveStatsCollector(Collector): 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 @@ class LiveStatsCollector(Collector): 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 @@ class LiveStatsCollector(Collector): 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 @@ class LiveStatsCollector(Collector): # 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 @@ class LiveStatsCollector(Collector): ) # 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 @@ class LiveStatsCollector(Collector): "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 @@ class LiveStatsCollector(Collector): 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 @@ class LiveStatsCollector(Collector): 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 ffc566a8a2a5..2af8caa2c2f6 100644 --- a/Lib/profiling/sampling/live_collector/widgets.py +++ b/Lib/profiling/sampling/live_collector/widgets.py @@ -180,7 +180,7 @@ class HeaderWidget(Widget): # 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 @@ class HeaderWidget(Widget): ) # 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 @@ class HeaderWidget(Widget): 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 @@ class HeaderWidget(Widget): 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 @@ class HeaderWidget(Widget): """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 @@ class HeaderWidget(Widget): 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 @@ class TableWidget(Widget): # 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 bfdc9d830cf1..04e6cd2f1fcb 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 @@ class TestLiveStatsCollectorPathSimplification(unittest.TestCase): 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 @@ class TestLiveStatsCollectorPathSimplification(unittest.TestCase): """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 @@ class TestLiveStatsCollectorFrameProcessing(unittest.TestCase): """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 @@ class TestLiveStatsCollectorFrameProcessing(unittest.TestCase): 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 @@ class TestLiveStatsCollectorFrameProcessing(unittest.TestCase): 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 @@ class TestLiveStatsCollectorFrameProcessing(unittest.TestCase): 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 @@ class TestLiveStatsCollectorFrameProcessing(unittest.TestCase): 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 @@ class TestLiveStatsCollectorFrameProcessing(unittest.TestCase): 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 @@ class TestLiveStatsCollectorCollect(unittest.TestCase): 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 @@ class TestLiveStatsCollectorCollect(unittest.TestCase): 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 @@ class TestLiveStatsCollectorStatisticsBuilding(unittest.TestCase): 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 @@ class TestLiveStatsCollectorStatisticsBuilding(unittest.TestCase): 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 @@ class TestLiveStatsCollectorStatisticsBuilding(unittest.TestCase): 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 @@ class TestLiveStatsCollectorStatisticsBuilding(unittest.TestCase): 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 @@ class TestLiveStatsCollectorStatisticsBuilding(unittest.TestCase): 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 @@ class TestLiveStatsCollectorStatisticsBuilding(unittest.TestCase): 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 @@ class TestLiveStatsCollectorFormatting(unittest.TestCase): 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 @@ class TestLiveStatsCollectorFormatting(unittest.TestCase): 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 @@ class TestLiveStatsCollectorFormatting(unittest.TestCase): 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 @@ class TestLiveStatsCollectorFormatting(unittest.TestCase): 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 3c226987323c..388f462cf21b 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 @@ class TestLiveCollectorInteractiveControls(unittest.TestCase): ) 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 @@ class TestLiveCollectorInteractiveControls(unittest.TestCase): """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 @@ class TestLiveCollectorInteractiveControls(unittest.TestCase): 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 @@ class TestLiveCollectorInteractiveControls(unittest.TestCase): 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 @@ class TestLiveCollectorFiltering(unittest.TestCase): """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 @@ class TestLiveCollectorFiltering(unittest.TestCase): """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 @@ class TestLiveCollectorFiltering(unittest.TestCase): """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 @@ class TestLiveCollectorFiltering(unittest.TestCase): """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 @@ class TestLiveCollectorFiltering(unittest.TestCase): """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 @@ class TestLiveCollectorFiltering(unittest.TestCase): """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 @@ class TestLiveCollectorFiltering(unittest.TestCase): """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 @@ class TestLiveCollectorFiltering(unittest.TestCase): """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 @@ class TestLiveCollectorThreadNavigation(unittest.TestCase): 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 @@ class TestLiveCollectorThreadNavigation(unittest.TestCase): 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 @@ class TestLiveCollectorThreadNavigation(unittest.TestCase): # 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 @@ class TestLiveCollectorThreadNavigation(unittest.TestCase): # 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 @@ class TestLiveCollectorThreadNavigation(unittest.TestCase): # 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 5c54022356b7..b5a387fa3a3a 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 @@ class TestLiveStatsCollectorWithMockDisplay(unittest.TestCase): 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 @@ class TestLiveStatsCollectorWithMockDisplay(unittest.TestCase): """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 @@ class TestLiveStatsCollectorWithMockDisplay(unittest.TestCase): 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 @@ class TestLiveStatsCollectorWithMockDisplay(unittest.TestCase): 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 @@ class TestLiveStatsCollectorWithMockDisplay(unittest.TestCase): 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 @@ class TestLiveStatsCollectorWithMockDisplay(unittest.TestCase): 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 @@ class TestLiveStatsCollectorWithMockDisplay(unittest.TestCase): 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 @@ class TestLiveStatsCollectorCursesIntegration(unittest.TestCase): 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 @@ class TestLiveStatsCollectorDisplayMethods(unittest.TestCase): } 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 @@ class TestLiveStatsCollectorDisplayMethods(unittest.TestCase): 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 @@ class TestLiveStatsCollectorDisplayMethods(unittest.TestCase): ) # 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 @@ class TestLiveStatsCollectorDisplayMethods(unittest.TestCase): # 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 @@ class TestLiveStatsCollectorDisplayMethods(unittest.TestCase): 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 @@ class TestLiveStatsCollectorDisplayMethods(unittest.TestCase): 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 @@ class TestLiveStatsCollectorDisplayMethods(unittest.TestCase): "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 @@ class TestLiveStatsCollectorDisplayMethods(unittest.TestCase): } 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 @@ class TestLiveStatsCollectorDisplayMethods(unittest.TestCase): "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 @@ class TestLiveStatsCollectorDisplayMethods(unittest.TestCase): } 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 @@ class TestLiveStatsCollectorDisplayMethods(unittest.TestCase): 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 @@ class TestLiveStatsCollectorDisplayMethods(unittest.TestCase): 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 @@ class TestLiveStatsCollectorDisplayMethods(unittest.TestCase): """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 @@ class TestLiveStatsCollectorDisplayMethods(unittest.TestCase): """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 @@ class TestLiveStatsCollectorEdgeCases(unittest.TestCase): "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 @@ class TestLiveStatsCollectorUpdateDisplay(unittest.TestCase): 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,