From 18edbf880cffbda979bb98adbb5d6a9c50c261cc Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Fri, 14 Nov 2025 18:15:30 +0000 Subject: [PATCH] fix(debugger): max match in probe file resolution (#15143) ## Description We change the probe source file path matching logic to return the longest matching path instead of the first result. This deals with cases where sources with the same name can be found on different entries of the Python path. ## Testing (cherry picked from commit 953ef8b7731dbe7a5f345f34105237790ce99dfe) --- ddtrace/debugging/_probe/model.py | 5 ++-- ...urce-file-resolution-bd73a5fd172c3711.yaml | 6 +++++ tests/debugging/probe/test_model.py | 24 +++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/debugging-probe-source-file-resolution-bd73a5fd172c3711.yaml diff --git a/ddtrace/debugging/_probe/model.py b/ddtrace/debugging/_probe/model.py index 32cfeaa9867..e5700ad324e 100644 --- a/ddtrace/debugging/_probe/model.py +++ b/ddtrace/debugging/_probe/model.py @@ -41,9 +41,8 @@ def _resolve_source_file(_path: str) -> Optional[Path]: if path.is_file(): return path.resolve() - for relpath in (path.relative_to(_) for _ in path.parents): - resolved_path = _resolve(relpath) - if resolved_path is not None: + for relpath in (path.relative_to(_) for _ in reversed(path.parents)): + if (resolved_path := _resolve(relpath)) is not None: return resolved_path return None diff --git a/releasenotes/notes/debugging-probe-source-file-resolution-bd73a5fd172c3711.yaml b/releasenotes/notes/debugging-probe-source-file-resolution-bd73a5fd172c3711.yaml new file mode 100644 index 00000000000..faaba551b02 --- /dev/null +++ b/releasenotes/notes/debugging-probe-source-file-resolution-bd73a5fd172c3711.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + dynamic instrumentation: fix issue with line probes matching the wrong + source file when multiple source files from different Python path entries + share the same name. diff --git a/tests/debugging/probe/test_model.py b/tests/debugging/probe/test_model.py index 729dc19d784..e7fb248c90f 100644 --- a/tests/debugging/probe/test_model.py +++ b/tests/debugging/probe/test_model.py @@ -1,4 +1,5 @@ from pathlib import Path +import sys from ddtrace.debugging._expressions import DDExpression from ddtrace.debugging._expressions import dd_compile @@ -57,3 +58,26 @@ def test_probe_hash(): ) assert hash(probe) + + +def test_resolve_source_file_same_filename_on_different_paths(tmp_path: Path): + """ + Test that if we have sources with the same name along different Python + paths, we resolve to the longest matching path. + """ + # Setup the file system for the test + (p := tmp_path / "a" / "b").mkdir(parents=True) + (q := tmp_path / "c" / "b").mkdir(parents=True) + + (fp := p / "test_model.py").touch() + (fq := q / "test_model.py").touch() + + # Patch the python path + original_pythonpath = sys.path + + try: + sys.path = [str(tmp_path / "c"), str(tmp_path)] + assert (r := _resolve_source_file("a/b/test_model.py")) is not None and r.resolve() == fp.resolve(), r + assert (r := _resolve_source_file("c/b/test_model.py")) is not None and r.resolve() == fq.resolve(), r + finally: + sys.path = original_pythonpath