diff --git a/Lib/profiling/sampling/gecko_collector.py b/Lib/profiling/sampling/gecko_collector.py index 2de8cce387e7f26..bc6ce6187b8846d 100644 --- a/Lib/profiling/sampling/gecko_collector.py +++ b/Lib/profiling/sampling/gecko_collector.py @@ -617,7 +617,7 @@ def _get_or_create_func(self, thread_data, filename, funcname, lineno): func_resources.append(resource_idx) if is_python: - filename_idx = self._intern_string(os.path.basename(filename)) + filename_idx = self._intern_string(filename) func_filenames.append(filename_idx) func_line_numbers.append(lineno) else: diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py index d4ba2df5044559f..b6c435b58ecfa83 100644 --- a/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py +++ b/Lib/test/test_profiling/test_sampling_profiler/test_collectors.py @@ -741,6 +741,51 @@ def test_gecko_collector_basic(self): self.assertGreater(stack_table["length"], 0) self.assertGreater(len(stack_table["frame"]), 0) + def test_gecko_collector_preserves_source_file_paths(self): + """Test that GeckoCollector preserves source file paths and distinguishes + files with the same basename in different directories.""" + collector = GeckoCollector(1000) + file_a = os.path.join("pkg_a", "__init__.py") + file_b = os.path.join("pkg_b", "__init__.py") + + test_frames = [ + MockInterpreterInfo( + 0, + [ + MockThreadInfo( + 1, + [ + MockFrameInfo(file_a, 10, "func_a"), + MockFrameInfo(file_b, 20, "func_b"), + ], + status=THREAD_STATUS_MAIN_THREAD, + ) + ], + ) + ] + collector.collect(test_frames) + profile_data = collector._build_profile() + + string_array = profile_data["shared"]["stringArray"] + func_table = profile_data["threads"][0]["funcTable"] + + self.assertEqual(func_table["length"], 2) + + funcs_by_name = { + string_array[func_table["name"][i]]: ( + func_table["fileName"][i], + string_array[func_table["fileName"][i]], + ) + for i in range(func_table["length"]) + } + + file_idx_a, resolved_file_a = funcs_by_name["func_a"] + file_idx_b, resolved_file_b = funcs_by_name["func_b"] + + self.assertEqual(resolved_file_a, file_a) + self.assertEqual(resolved_file_b, file_b) + self.assertNotEqual(file_idx_a, file_idx_b) + def test_gecko_collector_async_aware(self): collector = GeckoCollector(1000) diff --git a/Misc/NEWS.d/next/Library/2026-10-30-23-05-00.gh-issue-160976.GeCk0P.rst b/Misc/NEWS.d/next/Library/2026-10-30-23-05-00.gh-issue-160976.GeCk0P.rst new file mode 100644 index 000000000000000..1da8a1f97e6f98f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-30-23-05-00.gh-issue-160976.GeCk0P.rst @@ -0,0 +1 @@ +The Gecko reporter (``--gecko``) in ``profiling.sampling`` now records full source file paths instead of only basenames.