Skip to content

Commit

Permalink
Merge pull request #490 from impact27/canonic_inode
Browse files Browse the repository at this point in the history
PR: Use inodes for single canonic file path (Debugger)
  • Loading branch information
ccordoba12 committed May 24, 2024
2 parents 43d8cfa + e060492 commit ea75c0c
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 2 deletions.
21 changes: 21 additions & 0 deletions spyder_kernels/console/tests/test_console_kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -1391,5 +1391,26 @@ def test_django_settings(kernel):
assert "'settings':" in nsview


def test_hard_link_pdb(tmpdir):
"""
Test that breakpoints on a file are recognised even when the path is
different.
"""
# Create a file and a hard link
d = tmpdir.join("file.py")
d.write('def func():\n bb = "hello"\n')
folder = tmpdir.join("folder")
os.mkdir(folder)
hard_link = folder.join("file.py")
os.link(d, hard_link)

# Make sure both paths point to the same file
assert os.path.samefile(d, hard_link)

# Make sure canonic returns the same path for a single file
pdb_obj = SpyderPdb()
assert pdb_obj.canonic(str(d)) == pdb_obj.canonic(str(hard_link))


if __name__ == "__main__":
pytest.main()
42 changes: 40 additions & 2 deletions spyder_kernels/customize/spyderpdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ def __init__(self, completekey='tab', stdin=None, stdout=None,
# Fixes spyder-ide/spyder#20639.
self._predicates["debuggerskip"] = False

# Save seen files inodes
self._canonic_inode_to_filename = {}
self._canonic_filename_to_inode = {}

# --- Methods overriden for code execution
def print_exclamation_warning(self):
"""Print pdb warning for exclamation mark."""
Expand Down Expand Up @@ -621,8 +625,42 @@ def _cmdloop(self):

@lru_cache
def canonic(self, filename):
"""Return canonical form of filename."""
return super().canonic(filename)
"""
Return canonical form of filename.
In some case two path can point to the same file. For this reason
os.path.samefile uses os.stat. Here we normalise the path with os.stat
so a single path is returned for the same file.
see: https://docs.python.org/3/library/os.path.html#os.path.samefile
note: os.stat can be slow on windows so call it once per file.
"""
if filename == "<" + filename[1:-1] + ">":
return filename

filename = super().canonic(filename)

if filename in self._canonic_filename_to_inode:
inode = self._canonic_filename_to_inode[filename]
else:
try:
stat = os.stat(filename)
except OSError:
self._canonic_filename_to_inode[filename] = None
return filename

inode = (stat.st_dev, stat.st_ino)
if stat.st_ino == 0:
inode = None
self._canonic_filename_to_inode[filename] = inode
if inode is not None and inode not in self._canonic_inode_to_filename:
# First time this inode is seen
self._canonic_inode_to_filename[inode] = filename

if inode is None:
return filename
return self._canonic_inode_to_filename[inode]


def do_exitdb(self, arg):
"""Exit the debugger"""
Expand Down

0 comments on commit ea75c0c

Please sign in to comment.