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