mirror of
https://github.com/facebook/sapling.git
synced 2024-10-05 14:28:17 +03:00
f4da0e5eb5
Summary: eden doctor can discover when an inode is missing for a file, but can't remediate the issue. restart usually remediates the issue, but it would be better to have doctor remediate since restart can be very slow. We could do something similar to our remediation for phantom inodes (performing a filesystem operation). However, messing with the filesystem leaves us open to races with concurrent modifications. The point of the filesystem io is to make eden see a notification about a certain path and match it's state to the filesystem. So we can directly do that instead. We can more directly do this by introducing a thrift call to make eden match it's internal state to the filesystem. We could replace the other remediation with this thrift call. I'll leave that for a follow up. Reviewed By: xavierd Differential Revision: D46243633 fbshipit-source-id: a1df5929428dc4f6c8fd71d826fe1e7371ebf283
157 lines
4.7 KiB
Python
157 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
#
|
|
# This software may be used and distributed according to the terms of the
|
|
# GNU General Public License version 2.
|
|
|
|
from typing import List
|
|
|
|
from facebook.eden.ttypes import (
|
|
EdenError,
|
|
EdenErrorType,
|
|
FaultDefinition,
|
|
MatchFileSystemRequest,
|
|
MountId,
|
|
ScmFileStatus,
|
|
)
|
|
|
|
from .lib import prjfs_test, testcase
|
|
|
|
|
|
@testcase.eden_repo_test
|
|
class PrjfsMatchFsTest(prjfs_test.PrjFSTestBase):
|
|
"""Windows fsck integration tests"""
|
|
|
|
initial_commit: str = ""
|
|
enable_fault_injection: bool = True
|
|
|
|
def populate_repo(self) -> None:
|
|
self.repo.write_file("hello", "hola\n")
|
|
self.repo.write_file("adir/file", "foo!\n")
|
|
self.repo.write_file("subdir/bdir/file", "foo!\n")
|
|
self.repo.write_file("subdir/cdir/file", "foo!\n")
|
|
self.repo.write_file(".gitignore", "ignored/\n")
|
|
self.initial_commit = self.repo.commit("Initial commit.")
|
|
|
|
def select_storage_engine(self) -> str:
|
|
return "sqlite"
|
|
|
|
def get_initial_commit(self) -> str:
|
|
return self.initial_commit
|
|
|
|
def match_fs(self, files: List[bytes]) -> None:
|
|
with self.eden.get_thrift_client_legacy() as client:
|
|
errors = client.matchFilesystem(
|
|
MatchFileSystemRequest(
|
|
MountId(self.mount.encode()),
|
|
files,
|
|
)
|
|
)
|
|
for error in errors.results:
|
|
self.assertEqual(error.error, None)
|
|
|
|
def test_fix_no_problems(self) -> None:
|
|
self.assertNotInStatus(b"adir/file")
|
|
|
|
self.match_fs([b"adir/file"])
|
|
|
|
self.assertNotInStatus(b"adir/file")
|
|
|
|
def test_fix_missed_removal(self) -> None:
|
|
self.assertNotInStatus(b"adir/file")
|
|
|
|
with self.run_with_notifications_dropped_fault():
|
|
afile = self.mount_path / "adir" / "file"
|
|
afile.unlink()
|
|
|
|
self.assertNotInStatus(b"adir/file")
|
|
self.match_fs([b"adir/file"])
|
|
|
|
self.assertEqual(
|
|
self.eden_status(),
|
|
{b"adir/file": ScmFileStatus.REMOVED},
|
|
)
|
|
|
|
def test_fix_missed_addition(self) -> None:
|
|
self.assertNotInStatus(b"adir/anewfile")
|
|
|
|
with self.run_with_notifications_dropped_fault():
|
|
afile = self.mount_path / "adir" / "anewfile"
|
|
afile.touch()
|
|
|
|
self.assertNotInStatus(b"adir/anewfile")
|
|
self.match_fs([b"adir/anewfile"])
|
|
|
|
self.assertEqual(
|
|
self.eden_status(),
|
|
{b"adir/anewfile": ScmFileStatus.ADDED},
|
|
)
|
|
|
|
def test_fix_missed_directory_delete(self) -> None:
|
|
self.assertNotInStatus(b"adir/file")
|
|
|
|
with self.run_with_notifications_dropped_fault():
|
|
adir = self.mount_path / "adir"
|
|
afile = adir / "file"
|
|
afile.unlink()
|
|
adir.rmdir()
|
|
|
|
self.assertNotInStatus(b"adir/file")
|
|
self.match_fs([b"adir"])
|
|
|
|
self.assertEqual(
|
|
self.eden_status(),
|
|
{b"adir/file": ScmFileStatus.REMOVED},
|
|
)
|
|
|
|
def test_fix_missed_directory_addition(self) -> None:
|
|
self.assertNotInStatus(b"adir/asubdir/anewfile")
|
|
|
|
with self.run_with_notifications_dropped_fault():
|
|
asubdir = self.mount_path / "adir" / "asubdir"
|
|
afile = asubdir / "anewfile"
|
|
asubdir.mkdir()
|
|
afile.touch()
|
|
|
|
self.assertNotInStatus(b"adir/asubdir/anewfile")
|
|
self.match_fs([b"adir/asubdir"])
|
|
|
|
self.assertEqual(
|
|
self.eden_status(),
|
|
{b"adir/asubdir/anewfile": ScmFileStatus.ADDED},
|
|
)
|
|
|
|
def test_fix_failed(self) -> None:
|
|
self.assertNotInStatus(b"adir/file")
|
|
|
|
with self.run_with_notifications_dropped_fault():
|
|
afile = self.mount_path / "adir" / "file"
|
|
afile.unlink()
|
|
|
|
self.assertNotInStatus(b"adir/file")
|
|
|
|
with self.eden.get_thrift_client_legacy() as client:
|
|
client.injectFault(
|
|
FaultDefinition(
|
|
keyClass="PrjfsDispatcherImpl::fileNotification",
|
|
keyValueRegex=".*",
|
|
errorMessage="Blocked",
|
|
errorType="runtime_error",
|
|
)
|
|
)
|
|
errors = client.matchFilesystem(
|
|
MatchFileSystemRequest(
|
|
MountId(self.mount.encode()),
|
|
[b"adir/file"],
|
|
)
|
|
)
|
|
print(errors)
|
|
for error in errors.results:
|
|
self.assertEqual(
|
|
error.error,
|
|
EdenError(
|
|
message="class std::runtime_error: Blocked",
|
|
errorType=EdenErrorType.GENERIC_ERROR,
|
|
),
|
|
)
|