mirror of
https://github.com/facebook/sapling.git
synced 2025-01-01 09:37:56 +03:00
fbdb46f5cb
Reviewed By: chadaustin Differential Revision: D17872966 fbshipit-source-id: cd60a364a2146f0dadbeca693b1d4a5d7c97ff63
102 lines
4.0 KiB
Python
102 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
|
#
|
|
# This software may be used and distributed according to the terms of the
|
|
# GNU General Public License version 2.
|
|
|
|
import logging
|
|
import os
|
|
import pathlib
|
|
import stat
|
|
from typing import List
|
|
|
|
import eden.integration.lib.overlay as overlay_mod
|
|
from eden.integration.lib import testcase
|
|
|
|
|
|
class CorruptOverlayTest(testcase.HgRepoTestMixin, testcase.EdenRepoTest):
|
|
"""Test file operations when Eden's overlay is corrupted."""
|
|
|
|
def setUp(self) -> None:
|
|
super().setUp() # type: ignore (pyre does not follow python MRO properly)
|
|
self.overlay = overlay_mod.OverlayStore(self.eden, self.mount_path)
|
|
|
|
def populate_repo(self) -> None:
|
|
self.repo.write_file("src/committed_file", "committed_file content")
|
|
self.repo.write_file("readme.txt", "readme content")
|
|
self.repo.commit("Initial commit.")
|
|
|
|
def _corrupt_files(self) -> List[pathlib.Path]:
|
|
"""Corrupt some files inside the mount.
|
|
Returns relative paths to these files inside the mount.
|
|
"""
|
|
# Corrupt 3 separate files. 2 are tracked by mercurial, one is not.
|
|
# We will corrupt 2 of them by truncating the overlay file, and one by
|
|
# completely removing the overlay file. (In practice an unclean reboot often
|
|
# leaves overlay files that exist but have 0 length.)
|
|
tracked_path = pathlib.Path("src/committed_file")
|
|
untracked_path = pathlib.Path("src/new_file")
|
|
readme_path = pathlib.Path("readme.txt")
|
|
|
|
tracked_overlay_file_path = self.overlay.materialize_file(tracked_path)
|
|
untracked_overlay_file_path = self.overlay.materialize_file(untracked_path)
|
|
readme_overlay_file_path = self.overlay.materialize_file(readme_path)
|
|
|
|
self.eden.unmount(self.mount_path)
|
|
os.truncate(tracked_overlay_file_path, 0)
|
|
os.unlink(untracked_overlay_file_path)
|
|
os.truncate(readme_overlay_file_path, 0)
|
|
self.eden.mount(self.mount_path)
|
|
|
|
return [tracked_path, untracked_path, readme_path]
|
|
|
|
def test_unmount_succeeds(self) -> None:
|
|
corrupted_paths = self._corrupt_files()
|
|
|
|
# Access the files to make sure that edenfs loads them.
|
|
# The stat calls should succeed, but reading them would fail.
|
|
for path in corrupted_paths:
|
|
os.lstat(str(self.mount_path / path))
|
|
|
|
# Make sure that eden can successfully unmount the mount point
|
|
# Previously we had a bug where the inode unloading code would throw an
|
|
# exception if it failed to update the overlay state for some inodes.
|
|
self.eden.unmount(self.mount_path)
|
|
|
|
def test_unlink_deletes_corrupted_files(self) -> None:
|
|
corrupted_paths = self._corrupt_files()
|
|
for path in corrupted_paths:
|
|
logging.info(f"stat()ing and unlinking {path}")
|
|
full_path = self.mount_path / path
|
|
s = os.lstat(str(full_path))
|
|
self.assertTrue(stat.S_ISREG, s.st_mode)
|
|
self.assertEqual(0, s.st_mode & 0o7777)
|
|
self.assertEqual(0, s.st_size)
|
|
full_path.unlink()
|
|
self.assertFalse(
|
|
full_path.exists(), f"{full_path} should not exist after being deleted"
|
|
)
|
|
|
|
def test_mount_possible_after_corrupt_directory_and_cached_next_inode_number(
|
|
self
|
|
) -> None:
|
|
test_dir_path = self.mount_path / "test_dir"
|
|
test_dir_path.mkdir()
|
|
test_dir_overlay_file_path = self.overlay.materialize_dir(test_dir_path)
|
|
|
|
self.eden.unmount(self.mount_path)
|
|
os.truncate(test_dir_overlay_file_path, 0)
|
|
self.overlay.delete_cached_next_inode_number()
|
|
|
|
self.eden.mount(self.mount_path)
|
|
|
|
def test_eden_list_does_not_return_corrupt_mounts(self) -> None:
|
|
self.eden.shutdown()
|
|
|
|
# Truncate the overlay info file so edenfs will
|
|
# not be able to open the overlay
|
|
os.truncate(self.overlay.get_info_path(), 0)
|
|
|
|
self.eden.start()
|
|
self.assertEqual({str(self.mount): "NOT_RUNNING"}, self.eden.list_cmd_simple())
|