sapling/eden/integration/debug_getpath_test.py
Adam Simpkins 7c7aa119cd fix str/bytes conversion for thrift API calls
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
2018-11-14 13:03:09 -08:00

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)