sapling/eden/integration/prjfs_match_fs.py
Katie Mancini f4da0e5eb5 Add missing inodes in windows doctor
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
2023-06-09 11:52:50 -07:00

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,
),
)