mirror of
https://github.com/facebook/sapling.git
synced 2024-10-09 08:18:15 +03:00
7c7aa119cd
Summary: The thrift APIs accept path names and commit IDs as binary data (python bytes) rather than unicode strings. Our python code got this wrong in several locations. It looks like mypy didn't previously flag this since mypy doesn't actually figure out the correct type for the thrift `client` object, and seemed to just be largely ignoring it. I plan to update the code so that mypy can figure out out the client type correctly. Fixing these type errors is required to make sure we won't get type errors once that is changed. This just simply uses `encode("utf-8")` for now. In the future the path arguments should be converted to `pathlib.Path`, which will do a slightly smarter conversion, and avoid errors on non-UTF-8 binary data. In the meantime, I believe that just using `encode("utf-8")` preserves what the thrift code was doing implicitly before, and does not make handling of non-UTF-8 data any worse than it was before. Reviewed By: strager Differential Revision: D13051094 fbshipit-source-id: 94cb62f3dd78b8e854a72a392fe8fdfad5ffd4cb
137 lines
5.1 KiB
Python
137 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (c) 2016-present, Facebook, Inc.
|
|
# All rights reserved.
|
|
#
|
|
# This source code is licensed under the BSD-style license found in the
|
|
# LICENSE file in the root directory of this source tree. An additional grant
|
|
# of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
import os
|
|
import stat
|
|
import time
|
|
|
|
from facebook.eden.ttypes import TimeSpec
|
|
|
|
from .lib import edenclient, testcase
|
|
|
|
|
|
@testcase.eden_repo_test
|
|
class DebugGetPathTest(testcase.EdenRepoTest):
|
|
def populate_repo(self) -> None:
|
|
self.repo.write_file("hello", "hola\n")
|
|
self.repo.commit("Initial commit.")
|
|
|
|
def test_getpath_root_inode(self) -> None:
|
|
"""
|
|
Test that calling `eden debug getpath 1` returns the path to the eden
|
|
mount, and indicates that the inode is loaded.
|
|
"""
|
|
output = self.eden.run_cmd("debug", "getpath", "1", cwd=self.mount)
|
|
|
|
self.assertEqual("loaded " + self.mount + "\n", output)
|
|
|
|
def test_getpath_dot_eden_inode(self) -> None:
|
|
"""
|
|
Test that calling `eden debug getpath ${ino}` returns the path to the
|
|
.eden directory, and indicates that the inode is loaded.
|
|
"""
|
|
st = os.lstat(os.path.join(self.mount, ".eden"))
|
|
self.assertTrue(stat.S_ISDIR(st.st_mode))
|
|
ino = st.st_ino
|
|
|
|
output = self.eden.run_cmd("debug", "getpath", str(ino), cwd=self.mount)
|
|
|
|
self.assertEqual("loaded " + os.path.join(self.mount, ".eden") + "\n", output)
|
|
|
|
def test_getpath_invalid_inode(self) -> None:
|
|
"""
|
|
Test that calling `eden debug getpath 1234` raises an error since
|
|
1234 is not a valid inode number
|
|
"""
|
|
with self.assertRaises(edenclient.EdenCommandError) as context:
|
|
self.eden.run_cmd("debug", "getpath", "1234", cwd=self.mount)
|
|
self.assertIn(
|
|
"unknown inode number 1234", context.exception.stderr.decode()
|
|
)
|
|
|
|
def test_getpath_unloaded_inode(self) -> None:
|
|
"""
|
|
Test that calling `eden debug getpath` on an unloaded inode returns the
|
|
correct path and indicates that it is unloaded
|
|
"""
|
|
dirpath = os.path.join(self.mount, "dir")
|
|
filepath = os.path.join(dirpath, "file")
|
|
|
|
# Create the file
|
|
self.write_file(os.path.join("dir", "file"), "blah")
|
|
# Get the inodeNumber
|
|
stat = os.stat(filepath)
|
|
self.unload_one_inode_under("dir")
|
|
|
|
# Get the path for dir/file from its inodeNumber
|
|
output = self.eden.run_cmd("debug", "getpath", str(stat.st_ino), cwd=self.mount)
|
|
|
|
self.assertEqual(f"unloaded {filepath}\n", output)
|
|
|
|
def test_getpath_unloaded_inode_rename_parent(self) -> None:
|
|
"""
|
|
Test that when an unloaded inode has one of its parents renamed,
|
|
`eden debug getpath` returns the new path
|
|
"""
|
|
# Create the file
|
|
self.write_file(os.path.join("foo", "bar", "test.txt"), "blah")
|
|
dirpath = os.path.join(self.mount, "foo", "bar")
|
|
# Get the inodeNumber
|
|
stat = os.stat(os.path.join(dirpath, "test.txt"))
|
|
|
|
self.unload_one_inode_under(os.path.join("foo", "bar"))
|
|
|
|
# Rename the foo directory
|
|
os.rename(os.path.join(self.mount, "foo"), os.path.join(self.mount, "newname"))
|
|
# Get the new path for the file from its inodeNumber
|
|
output = self.eden.run_cmd("debug", "getpath", str(stat.st_ino), cwd=self.mount)
|
|
|
|
self.assertEqual(
|
|
"unloaded " + os.path.join(self.mount, "newname", "bar", "test.txt") + "\n",
|
|
output,
|
|
)
|
|
|
|
def unload_one_inode_under(self, path: str) -> None:
|
|
# TODO: To support unloading more than one inode, sum the return value
|
|
# until count is reached our the attempt limit has been reached.
|
|
remaining_attempts = 5
|
|
while True:
|
|
age = TimeSpec() # zero
|
|
with self.eden.get_thrift_client() as client:
|
|
count = client.unloadInodeForPath(
|
|
os.fsencode(self.mount), os.fsencode(path), age
|
|
)
|
|
if remaining_attempts == 1:
|
|
self.assertEqual(1, count)
|
|
elif count == 1:
|
|
break
|
|
else:
|
|
remaining_attempts -= 1
|
|
time.sleep(1)
|
|
continue
|
|
|
|
def test_getpath_unlinked_inode(self) -> None:
|
|
"""
|
|
Test that when an inode is unlinked, `eden debug getpath` indicates
|
|
that it is unlinked
|
|
"""
|
|
# Create the file
|
|
self.write_file(os.path.join("foo", "bar", "test.txt"), "blah")
|
|
# Keep an open file handle so that the inode doesn't become invalid
|
|
f = open(os.path.join(self.mount, "foo", "bar", "test.txt"))
|
|
# Get the inodeNumber
|
|
stat = os.stat(os.path.join(self.mount, "foo", "bar", "test.txt"))
|
|
# Unlink the file
|
|
os.unlink(os.path.join(self.mount, "foo", "bar", "test.txt"))
|
|
output = self.eden.run_cmd("debug", "getpath", str(stat.st_ino), cwd=self.mount)
|
|
# Close the file handle
|
|
f.close()
|
|
|
|
self.assertEqual("loaded [unlinked]\n", output)
|