def check_loaded_content( tracker: ProblemTracker, instance: EdenInstance, checkout: EdenCheckout, query_prjfs_file: Callable[[Path], PRJ_FILE_STATE], ) -> None: with instance.get_thrift_client_legacy() as client: loaded = client.debugInodeStatus( bytes(checkout.path), b"", flags=DIS_REQUIRE_LOADED, sync=SyncBehavior(), ) errors = [] for loaded_dir in loaded: path = Path(os.fsdecode(loaded_dir.path)) for dirent in loaded_dir.entries: if not stat.S_ISREG(dirent.mode) or dirent.materialized: continue dirent_path = path / Path(os.fsdecode(dirent.name)) filestate = query_prjfs_file(checkout.path / dirent_path) if (filestate & PRJ_FILE_STATE.HydratedPlaceholder != PRJ_FILE_STATE.HydratedPlaceholder): # We should only compute the sha1 of files that have been read. continue def compute_file_sha1(file: Path) -> bytes: hasher = hashlib.sha1() with open(checkout.path / dirent_path, "rb") as f: while True: buf = f.read(1024 * 1024) if buf == b"": break hasher.update(buf) return hasher.digest() sha1 = client.getSHA1(bytes(checkout.path), [bytes(dirent_path)], sync=SyncBehavior())[0].get_sha1() on_disk_sha1 = compute_file_sha1(checkout.path / dirent_path) if sha1 != on_disk_sha1: errors += [(dirent_path, sha1, on_disk_sha1)] if errors != []: tracker.add_problem(LoadedFileHasDifferentContentOnDisk(errors))
def _assert_thrift_calls_fail_during_mount_init( self, client: EdenClient) -> None: error_regex = "mount point .* is still initializing" mount_path = Path(self.mount) null_commit = b"\00" * 20 with self.assertRaisesRegex(EdenError, error_regex) as ctx: client.getFileInformation(mountPoint=bytes(mount_path), paths=[b""], sync=SyncBehavior()) self.assertEqual(EdenErrorType.POSIX_ERROR, ctx.exception.errorType) with self.assertRaisesRegex(EdenError, error_regex) as ctx: client.getScmStatus(mountPoint=bytes(mount_path), listIgnored=False, commit=null_commit) self.assertEqual(EdenErrorType.POSIX_ERROR, ctx.exception.errorType) parents = WorkingDirectoryParents(parent1=null_commit) params = ResetParentCommitsParams() with self.assertRaisesRegex(EdenError, error_regex) as ctx: client.resetParentCommits(mountPoint=bytes(mount_path), parents=parents, params=params) self.assertEqual(EdenErrorType.POSIX_ERROR, ctx.exception.errorType)
def test_get_sha1_throws_for_non_existent_file(self) -> None: with self.get_thrift_client_legacy() as client: results = client.getSHA1(self.mount_path_bytes, [b"i_do_not_exist"], sync=SyncBehavior()) self.assertEqual(1, len(results)) self.assert_sha1_error(results[0], "i_do_not_exist: No such file or directory")
def test_get_sha1_throws_for_symlink(self) -> None: """Fails because caller should resolve the symlink themselves.""" with self.get_thrift_client_legacy() as client: results = client.getSHA1(self.mount_path_bytes, [b"slink"], sync=SyncBehavior()) self.assertEqual(1, len(results)) self.assert_sha1_error(results[0], "slink: file is a symlink: Invalid argument")
def test_get_sha1_throws_for_path_with_dot_components(self) -> None: with self.get_thrift_client_legacy() as client: results = client.getSHA1(self.mount_path_bytes, [b"./hello"], sync=SyncBehavior()) self.assertEqual(1, len(results)) self.assert_sha1_error( results[0], re.compile( r".*PathComponentValidationError.*: PathComponent must not be \." ), )
def get_loaded_inodes_count(self, path: str) -> int: with self.get_thrift_client_legacy() as client: result = client.debugInodeStatus(self.mount_path_bytes, os.fsencode(path), flags=0, sync=SyncBehavior()) inode_count = 0 for item in result: assert item.entries is not None for inode in item.entries: if inode.loaded: inode_count += 1 return inode_count
def check_materialized_are_accessible( tracker: ProblemTracker, instance: EdenInstance, checkout: EdenCheckout, ) -> None: mismatched_mode = [] inaccessible_inodes = [] with instance.get_thrift_client_legacy() as client: materialized = client.debugInodeStatus( bytes(checkout.path), b"", flags=DIS_REQUIRE_MATERIALIZED, sync=SyncBehavior(), ) for materialized_dir in materialized: path = Path(os.fsdecode(materialized_dir.path)) try: st = os.lstat(checkout.path / path) except OSError: inaccessible_inodes += [path] continue if not stat.S_ISDIR(st.st_mode): mismatched_mode += [(path, stat.S_IFDIR, st.st_mode)] for dirent in materialized_dir.entries: if dirent.materialized: dirent_path = path / Path(os.fsdecode(dirent.name)) try: dirent_stat = os.lstat(checkout.path / dirent_path) except OSError: inaccessible_inodes += [dirent_path] continue # TODO(xavierd): Symlinks are for now recognized as files. dirent_mode = (stat.S_IFREG if stat.S_ISLNK( dirent_stat.st_mode) else stat.S_IFMT(dirent_stat.st_mode)) if dirent_mode != stat.S_IFMT(dirent.mode): mismatched_mode += [(dirent_path, dirent_stat.st_mode, dirent.mode)] if inaccessible_inodes != []: tracker.add_problem( MaterializedInodesAreInaccessible(inaccessible_inodes)) if mismatched_mode != []: tracker.add_problem( MaterializedInodesHaveDifferentModeOnDisk(mismatched_mode))
def get_paths_for_inodes(self, path: str = "") -> Iterable[str]: """path: relative path to a directory under the mount.""" with self.create_thrift_client() as client: debug_info = client.debugInodeStatus( os.fsencode(self._mount_point), os.fsencode(path), flags=0, sync=SyncBehavior(), ) for tree_inode_debug_info in debug_info: parent_dir = tree_inode_debug_info.path.decode("utf-8") for entry in tree_inode_debug_info.entries: if entry.loaded: yield f'{parent_dir}/{entry.name.decode("utf-8")}'
def test_get_sha1(self) -> None: expected_sha1_for_hello = hashlib.sha1(b"hola\n").digest() result_for_hello = SHA1Result(expected_sha1_for_hello) expected_sha1_for_adir_file = hashlib.sha1(b"foo!\n").digest() result_for_adir_file = SHA1Result(expected_sha1_for_adir_file) with self.get_thrift_client_legacy() as client: self.assertEqual( [result_for_hello, result_for_adir_file], client.getSHA1( self.mount_path_bytes, [b"hello", b"adir/file"], sync=SyncBehavior(), ), )
def get_inode_count(self, path: str = "") -> int: """path: relative path to a directory under the mount. Use '' for the root. Note that this will include the inode count for the root .hg and .eden entries. """ with self.create_thrift_client() as client: debug_info = client.debugInodeStatus( os.fsencode(self._mount_point), os.fsencode(path), flags=0, sync=SyncBehavior(), ) count = 0 for tree_inode_debug_info in debug_info: count += sum(1 for entry in tree_inode_debug_info.entries if entry.loaded) return count
def xtest_debug_blob_prints_binary_data(self) -> None: with self.eden.get_thrift_client_legacy() as client: debugInfo = client.debugInodeStatus(os.fsencode(self.mount), b".", flags=0, sync=SyncBehavior()) [root] = [entry for entry in debugInfo if entry.path == b""] self.assertEqual(1, root.inodeNumber) [file] = [entry for entry in root.entries if entry.name == b"binary"] self.assertEqual(False, file.materialized) blob_id = binascii.hexlify(file.hash).decode() print(blob_id) output = self.eden.run_cmd("debug", "blob", ".", blob_id, cwd=self.mount) self.assertEqual(b"\xff\xfe\xfd\xfc", output)
def test_get_sha1_throws_for_directory(self) -> None: with self.get_thrift_client_legacy() as client: results = client.getSHA1(self.mount_path_bytes, [b"adir"], sync=SyncBehavior(60)) self.assertEqual(1, len(results)) self.assert_sha1_error(results[0], "adir: Is a directory")
def test_get_sha1_throws_for_empty_string(self) -> None: with self.get_thrift_client_legacy() as client: results = client.getSHA1(self.mount_path_bytes, [b""], sync=SyncBehavior()) self.assertEqual(1, len(results)) self.assert_sha1_error(results[0], "path cannot be the empty string")