def setUp(self): self.active_mounts: List[bytes] = [b'/mnt/active1', b'/mnt/active2'] self.mount_table = FakeMountTable() self.mount_table.stats['/mnt/active1'] = mtab.MTStat( st_uid=os.getuid(), st_dev=10) self.mount_table.stats['/mnt/active2'] = mtab.MTStat( st_uid=os.getuid(), st_dev=11) self.check = doctor.StaleMountsCheck( active_mount_points=self.active_mounts, mount_table=self.mount_table)
def test_unconfigured_mounts_dont_crash(self): # If Eden advertises that a mount is active, but it is not in the # configuration, then at least don't throw an exception. tmp_dir = tempfile.mkdtemp(prefix='eden_test.') try: edenfs_path1 = os.path.join(tmp_dir, 'path1') edenfs_path2 = os.path.join(tmp_dir, 'path2') mount_paths = OrderedDict() mount_paths[edenfs_path1] = { 'bind-mounts': {}, 'mount': edenfs_path1, 'scm_type': 'hg', 'snapshot': 'abcd' * 10, 'client-dir': '/I_DO_NOT_EXIST1' } # path2 is not configured in the config... config = FakeConfig(mount_paths, is_healthy=True) # ... but is advertised by the daemon... config.get_thrift_client()._mounts = [ eden_ttypes.MountInfo(mountPoint=edenfs_path1), eden_ttypes.MountInfo(mountPoint=edenfs_path2), ] # ... and is in the system mount table. mount_table = FakeMountTable() mount_table.stats[edenfs_path1] = mtab.MTStat(st_uid=os.getuid(), st_dev=11) mount_table.stats[edenfs_path2] = mtab.MTStat(st_uid=os.getuid(), st_dev=12) os.mkdir(edenfs_path1) hg_dir = os.path.join(edenfs_path1, '.hg') os.mkdir(hg_dir) dirstate = os.path.join(hg_dir, 'dirstate') dirstate_hash = b'\xab\xcd' * 10 parents = (dirstate_hash, b'\x00' * 20) with open(dirstate, 'wb') as f: eden.dirstate.write(f, parents, tuples_dict={}, copymap={}) dry_run = False out = io.StringIO() exit_code = doctor.cure_what_ails_you(config, dry_run, out, mount_table) finally: shutil.rmtree(tmp_dir) self.assertEqual( f'''\ Performing 3 checks for {edenfs_path1}. All is well. ''', out.getvalue()) self.assertEqual(0, exit_code)
def test_not_all_mounts_have_watchman_watcher(self, mock_watchman): edenfs_path = '/path/to/eden-mount' edenfs_path_not_watched = '/path/to/eden-mount-not-watched' side_effects: List[Dict[str, Any]] = [] calls = [] calls.append(call(['watch-list'])) side_effects.append({'roots': [edenfs_path]}) calls.append(call(['watch-project', edenfs_path])) side_effects.append({'watcher': 'eden'}) calls.append(call(['debug-get-subscriptions', edenfs_path])) side_effects.append({}) mock_watchman.side_effect = side_effects out = io.StringIO() dry_run = False mount_paths = OrderedDict() mount_paths[edenfs_path] = { 'bind-mounts': {}, 'mount': edenfs_path, 'scm_type': 'git', 'snapshot': 'abcd' * 10, 'client-dir': '/I_DO_NOT_EXIST' } mount_paths[edenfs_path_not_watched] = { 'bind-mounts': {}, 'mount': edenfs_path_not_watched, 'scm_type': 'git', 'snapshot': 'abcd' * 10, 'client-dir': '/I_DO_NOT_EXIST' } config = FakeConfig(mount_paths, is_healthy=True) config.get_thrift_client()._mounts = [ eden_ttypes.MountInfo(mountPoint=edenfs_path), eden_ttypes.MountInfo(mountPoint=edenfs_path_not_watched), ] mount_table = FakeMountTable() mount_table.stats['/path/to/eden-mount'] = mtab.MTStat( st_uid=os.getuid(), st_dev=10) mount_table.stats['/path/to/eden-mount-not-watched'] = mtab.MTStat( st_uid=os.getuid(), st_dev=11) exit_code = doctor.cure_what_ails_you(config, dry_run, out, mount_table=mount_table) self.assertEqual( 'Performing 2 checks for /path/to/eden-mount.\n' 'Performing 2 checks for /path/to/eden-mount-not-watched.\n' 'All is well.\n', out.getvalue()) mock_watchman.assert_has_calls(calls) self.assertEqual(0, exit_code)
def test_bind_mounts_okay(self): mount_table = FakeMountTable() mount_table.stats[self.fbsource_bind_mounts] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.edenfs_path1] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) # Client bind mount paths (under .eden) mount_table.stats[self.client_bm1] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.client_bm2] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.client_bm3] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) # Bind mount paths (under eden path) mount_table.stats[self.bm1] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.bm2] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.bm3] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) fixer, out = self.run_check(mount_table, dry_run=False) self.assertEqual("", out) self.assert_results(fixer, num_problems=0)
def test_bind_mounts_and_client_dir_missing(self): mount_table = FakeMountTable() mount_table.stats[self.fbsource_bind_mounts] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.edenfs_path1] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) # Client bind mount paths (under .eden) mount_table.stats[self.client_bm1] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) # Bind mount paths (under eden path) mount_table.stats[self.bm1] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) # These bound mind operations will succeed. mount_table.bind_mount_success_paths[self.client_bm2] = self.bm2 mount_table.bind_mount_success_paths[self.client_bm3] = self.bm3 fixer, out = self.run_check(mount_table, dry_run=False) self.assertEqual( f"""\ <yellow>- Found problem:<reset> Missing client directory for bind mount {self.fbsource_bind_mounts}/fbandroid-buck-out Creating directory {self.fbsource_bind_mounts}/fbandroid-buck-out...<green>fixed<reset> <yellow>- Found problem:<reset> Bind mount at {self.edenfs_path1}/fbandroid/buck-out is not mounted Remounting bind mount at {self.edenfs_path1}/fbandroid/buck-out...<green>fixed<reset> <yellow>- Found problem:<reset> Missing client directory for bind mount {self.fbsource_bind_mounts}/buck-out Creating directory {self.fbsource_bind_mounts}/buck-out...<green>fixed<reset> <yellow>- Found problem:<reset> Bind mount at {self.edenfs_path1}/buck-out is not mounted Remounting bind mount at {self.edenfs_path1}/buck-out...<green>fixed<reset> """, out, ) self.assert_results(fixer, num_problems=4, num_fixed_problems=4)
def test_does_not_unmount_mounts_with_same_device_as_active_mount(self): self.mount_table.set_eden_mounts(self.active_mounts + [b'/mnt/stale1']) self.mount_table.stats['/mnt/stale1'] = mtab.MTStat(st_uid=os.getuid(), st_dev=10) result = self.check.do_check(dry_run=False) self.assertEqual('', result.message) self.assertEqual(doctor.CheckResultType.NO_ISSUE, result.result_type) self.assertEqual([], self.mount_table.unmount_lazy_calls) self.assertEqual([], self.mount_table.unmount_force_calls)
def test_bind_mounts_and_client_dir_missing_dry_run(self): mount_table = FakeMountTable() mount_table.stats[self.fbsource_bind_mounts] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.edenfs_path1] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) # Client bind mount paths (under .eden) mount_table.stats[self.client_bm1] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) # Bind mount paths (under eden path) mount_table.stats[self.bm1] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) fixer, out = self.run_check(mount_table, dry_run=True) self.assertEqual( f"""\ <yellow>- Found problem:<reset> Missing client directory for bind mount {self.fbsource_bind_mounts}/fbandroid-buck-out Would create directory {self.fbsource_bind_mounts}/fbandroid-buck-out <yellow>- Found problem:<reset> Bind mount at {self.edenfs_path1}/fbandroid/buck-out is not mounted Would remount bind mount at {self.edenfs_path1}/fbandroid/buck-out <yellow>- Found problem:<reset> Missing client directory for bind mount {self.fbsource_bind_mounts}/buck-out Would create directory {self.fbsource_bind_mounts}/buck-out <yellow>- Found problem:<reset> Bind mount at {self.edenfs_path1}/buck-out is not mounted Would remount bind mount at {self.edenfs_path1}/buck-out """, out, ) self.assert_results(fixer, num_problems=4)
def test_dry_run_prints_stale_mounts_and_does_not_unmount(self): self.mount_table.set_eden_mounts(self.active_mounts + [b'/mnt/stale2', b'/mnt/stale1']) self.mount_table.stats['/mnt/stale1'] = mtab.MTStat(st_uid=os.getuid(), st_dev=12) self.mount_table.stats['/mnt/stale2'] = mtab.MTStat(st_uid=os.getuid(), st_dev=13) result = self.check.do_check(dry_run=True) self.assertEqual(doctor.CheckResultType.NOT_FIXED_BECAUSE_DRY_RUN, result.result_type) self.assertEqual( dedent('''\ Found 2 stale edenfs mount points: /mnt/stale1 /mnt/stale2 Not unmounting because dry run. '''), result.message) self.assertEqual([], self.mount_table.unmount_lazy_calls) self.assertEqual([], self.mount_table.unmount_force_calls)
def test_bind_mount_wrong_device(self): # bm1, bm2 should not have same device as edenfs mount_table = FakeMountTable() mount_table.stats[self.fbsource_bind_mounts] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.edenfs_path1] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) # Client bind mount paths (under .eden) mount_table.stats[self.client_bm1] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.client_bm2] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.client_bm3] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) # Bind mount paths (under eden path) mount_table.stats[self.bm1] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) mount_table.stats[self.bm2] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) mount_table.stats[self.bm3] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) # These bound mind operations will succeed. mount_table.bind_mount_success_paths[self.client_bm1] = self.bm1 mount_table.bind_mount_success_paths[self.client_bm2] = self.bm2 fixer, out = self.run_check(mount_table, dry_run=False) self.assertEqual( f"""\ <yellow>- Found problem:<reset> Bind mount at {self.edenfs_path1}/fbcode/buck-out is not mounted Remounting bind mount at {self.edenfs_path1}/fbcode/buck-out...<green>fixed<reset> <yellow>- Found problem:<reset> Bind mount at {self.edenfs_path1}/fbandroid/buck-out is not mounted Remounting bind mount at {self.edenfs_path1}/fbandroid/buck-out...<green>fixed<reset> """, out, ) self.assert_results(fixer, num_problems=2, num_fixed_problems=2)
def test_force_unmounts_if_lazy_fails(self): self.mount_table.set_eden_mounts(self.active_mounts + [b'/mnt/stale1', b'/mnt/stale2']) self.mount_table.fail_unmount_lazy(b'/mnt/stale1') self.mount_table.stats['/mnt/stale1'] = mtab.MTStat(st_uid=os.getuid(), st_dev=12) self.mount_table.stats['/mnt/stale2'] = mtab.MTStat(st_uid=os.getuid(), st_dev=13) result = self.check.do_check(dry_run=False) self.assertEqual( dedent('''\ Unmounted 2 stale edenfs mount points: /mnt/stale1 /mnt/stale2 '''), result.message) self.assertEqual(doctor.CheckResultType.FIXED, result.result_type) self.assertEqual([b'/mnt/stale1', b'/mnt/stale2'], self.mount_table.unmount_lazy_calls) self.assertEqual([b'/mnt/stale1'], self.mount_table.unmount_force_calls)
def add_mount( self, path: str, uid: Optional[int] = None, dev: Optional[int] = None, mode: Optional[int] = None, device: str = "edenfs", vfstype: str = "fuse", ) -> None: if uid is None: uid = os.getuid() if dev is None: dev = self._next_dev self._next_dev += 1 if mode is None: mode = 16877 self._add_mount_info(path, device=device, vfstype=vfstype) self.stats[path] = mtab.MTStat(st_uid=uid, st_dev=dev, st_mode=mode) if device == "edenfs": self.stats[os.path.join(path, ".eden")] = mtab.MTStat(st_uid=uid, st_dev=dev, st_mode=mode)
def test_bind_mount_wrong_device_dry_run(self): # bm1, bm2 should not have same device as edenfs mount_table = FakeMountTable() mount_table.stats[self.fbsource_bind_mounts] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.edenfs_path1] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) # Client bind mount paths (under .eden) mount_table.stats[self.client_bm1] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.client_bm2] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.client_bm3] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) # Bind mount paths (under eden path) mount_table.stats[self.bm1] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) mount_table.stats[self.bm2] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) mount_table.stats[self.bm3] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) fixer, out = self.run_check(mount_table, dry_run=True) self.assertEqual( f"""\ <yellow>- Found problem:<reset> Bind mount at {self.edenfs_path1}/fbcode/buck-out is not mounted Would remount bind mount at {self.edenfs_path1}/fbcode/buck-out <yellow>- Found problem:<reset> Bind mount at {self.edenfs_path1}/fbandroid/buck-out is not mounted Would remount bind mount at {self.edenfs_path1}/fbandroid/buck-out """, out, ) self.assert_results(fixer, num_problems=2)
def test_fails_if_unmount_fails(self): self.mount_table.set_eden_mounts(self.active_mounts + [b'/mnt/stale1', b'/mnt/stale2']) self.mount_table.stats['/mnt/stale1'] = mtab.MTStat(st_uid=os.getuid(), st_dev=12) self.mount_table.stats['/mnt/stale2'] = mtab.MTStat(st_uid=os.getuid(), st_dev=13) self.mount_table.fail_unmount_lazy(b'/mnt/stale1', b'/mnt/stale2') self.mount_table.fail_unmount_force(b'/mnt/stale1') result = self.check.do_check(dry_run=False) self.assertEqual(doctor.CheckResultType.FAILED_TO_FIX, result.result_type) self.assertEqual( dedent('''\ Successfully unmounted 1 mount point: /mnt/stale2 Failed to unmount 1 mount point: /mnt/stale1 '''), result.message) self.assertEqual([b'/mnt/stale1', b'/mnt/stale2'], self.mount_table.unmount_lazy_calls) self.assertEqual([b'/mnt/stale1', b'/mnt/stale2'], self.mount_table.unmount_force_calls)
def test_client_bind_mount_multiple_issues(self): # Bind mount 1 does not exist # Bind mount 2 has wrong device type # Bind mount 3 is a file instead of a directory mount_table = FakeMountTable() mount_table.stats[self.fbsource_bind_mounts] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) # Client bind mount paths (under .eden) mount_table.stats[self.edenfs_path1] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) mount_table.stats[self.client_bm2] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) mount_table.stats[self.client_bm3] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=33188 ) # Bind mount paths (under eden path) mount_table.stats[self.bm1] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.bm2] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.bm3] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) fixer, out = self.run_check(mount_table, dry_run=False) self.assertEqual( f"""\ <yellow>- Found problem:<reset> Missing client directory for bind mount {self.fbsource_bind_mounts}/fbcode-buck-out Creating directory {self.fbsource_bind_mounts}/fbcode-buck-out...<green>fixed<reset> <yellow>- Found problem:<reset> Expected {self.fbsource_bind_mounts}/buck-out to be a directory Please remove the file at {self.fbsource_bind_mounts}/buck-out """, out, ) self.assert_results( fixer, num_problems=2, num_fixed_problems=1, num_manual_fixes=1 )
def test_mount_path_not_dir(self): mount_table = FakeMountTable() mount_table.stats[self.fbsource_bind_mounts] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.edenfs_path1] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) # Client bind mount paths (under .eden) mount_table.stats[self.client_bm1] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.client_bm2] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.client_bm3] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) # Bind mount paths (under eden path) # Note: bm3 is not a directory mount_table.stats[self.bm1] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.bm2] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.bm3] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=33188 ) fixer, out = self.run_check(mount_table, dry_run=False) self.assertEqual( f"""\ <yellow>- Found problem:<reset> Expected {self.edenfs_path1}/buck-out to be a directory Please remove the file at {self.edenfs_path1}/buck-out """, out, ) self.assert_results(fixer, num_problems=1, num_manual_fixes=1)
def test_client_bind_mounts_missing_fail(self): mount_table = FakeMountTable() fs_util = FakeFsUtil() mount_table.stats[self.fbsource_bind_mounts] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.edenfs_path1] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) # Client bind mount paths (under .eden) mount_table.stats[self.client_bm2] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) # Bind mount paths (under eden path) mount_table.stats[self.bm1] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.bm2] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.bm3] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) fs_util.path_error[self.client_bm3] = "Failed to create directory" fixer, out = self.run_check(mount_table, dry_run=False, fs_util=fs_util) self.assertEqual( f"""\ <yellow>- Found problem:<reset> Missing client directory for bind mount {self.fbsource_bind_mounts}/fbcode-buck-out Creating directory {self.fbsource_bind_mounts}/fbcode-buck-out...<green>fixed<reset> <yellow>- Found problem:<reset> Missing client directory for bind mount {self.fbsource_bind_mounts}/buck-out Creating directory {self.fbsource_bind_mounts}/buck-out...<red>error<reset> Failed to fix problem: Failed to create directory """, out, ) self.assert_results( fixer, num_problems=2, num_fixed_problems=1, num_failed_fixes=1 )
def create_test_mount( self, path: str, snapshot: Optional[str] = None, bind_mounts: Optional[Dict[str, str]] = None, client_name: Optional[str] = None, scm_type: str = "hg", active: bool = True, setup_path: bool = True, dirstate_parent: Union[str, Tuple[str, str], None] = None, backing_repo: Optional[Path] = None, ) -> EdenCheckout: """ Define a configured mount. If active is True and status was set to ALIVE when creating the FakeClient then the mount will appear as a normal active mount. It will be reported in the thrift results and the mount table, and the mount directory will be populated with a .hg/ or .git/ subdirectory. The setup_path argument can be set to False to prevent creating the fake mount directory on disk. Returns the absolute path to the mount directory. """ full_path = os.path.join(self._tmp_dir, path) if full_path in self._checkouts_by_path: raise Exception(f"duplicate mount definition: {full_path}") if snapshot is None: snapshot = self.default_commit_hash if bind_mounts is None: bind_mounts = {} if client_name is None: client_name = path.replace("/", "_") backing_repo_path = (backing_repo if backing_repo is not None else self.default_backing_repo) state_dir = self.clients_path / client_name assert full_path not in self._checkouts_by_path config = CheckoutConfig( backing_repo=backing_repo_path, scm_type=scm_type, bind_mounts=bind_mounts, default_revision=snapshot, redirections={}, ) checkout = FakeCheckout(state_dir=state_dir, config=config, snapshot=snapshot) self._checkouts_by_path[full_path] = checkout # Write out the config file and snapshot file state_dir.mkdir() eden_checkout = EdenCheckout(typing.cast(EdenInstance, self), Path(full_path), state_dir) eden_checkout.save_config(config) eden_checkout.save_snapshot(snapshot) if active and self._status == fb303_status.ALIVE: # Report the mount in /proc/mounts dev_id = self._next_dev_id self._next_dev_id += 1 self.mount_table.stats[full_path] = mtab.MTStat( st_uid=os.getuid(), st_dev=dev_id, st_mode=(stat.S_IFDIR | 0o755)) # Tell the thrift client to report the mount as active self._fake_client._mounts.append( eden_ttypes.MountInfo( mountPoint=os.fsencode(full_path), edenClientPath=os.fsencode(state_dir), state=eden_ttypes.MountState.RUNNING, )) # Set up directories on disk that look like the mounted checkout if setup_path: os.makedirs(full_path) if scm_type == "hg": self._setup_hg_path(full_path, checkout, dirstate_parent) elif scm_type == "git": os.mkdir(os.path.join(full_path, ".git")) return EdenCheckout(typing.cast(EdenInstance, self), Path(full_path), Path(state_dir))
def test_bind_mounts_missing_fail(self): mount_table = FakeMountTable() mount_table.stats[self.fbsource_bind_mounts] = mtab.MTStat( st_uid=os.getuid(), st_dev=11, st_mode=16877 ) mount_table.stats[self.edenfs_path1] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) # Client bind mount paths (under .eden) mount_table.stats[self.client_bm1] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) mount_table.stats[self.client_bm2] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) mount_table.stats[self.client_bm3] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) # Bind mount paths (under eden path) mount_table.stats[self.bm1] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) mount_table.stats[self.bm2] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) mount_table.stats[self.bm3] = mtab.MTStat( st_uid=os.getuid(), st_dev=12, st_mode=16877 ) # These bound mind operations will succeed. mount_table.bind_mount_success_paths[self.client_bm1] = self.bm1 fixer, out = self.run_check(mount_table, dry_run=False) self.assertEqual( f"""\ <yellow>- Found problem:<reset> Bind mount at {self.edenfs_path1}/fbcode/buck-out is not mounted Remounting bind mount at {self.edenfs_path1}/fbcode/buck-out...<green>fixed<reset> <yellow>- Found problem:<reset> Bind mount at {self.edenfs_path1}/fbandroid/buck-out is not mounted Remounting bind mount at {self.edenfs_path1}/fbandroid/buck-out...<red>error<reset> Failed to fix problem: Command 'sudo mount -o bind \ {self.fbsource_bind_mounts}/fbandroid-buck-out \ {self.edenfs_path1}/fbandroid/buck-out' returned non-zero exit status 1. <yellow>- Found problem:<reset> Bind mount at {self.edenfs_path1}/buck-out is not mounted Remounting bind mount at {self.edenfs_path1}/buck-out...<red>error<reset> Failed to fix problem: Command \ 'sudo mount -o bind {self.fbsource_bind_mounts}/buck-out \ {self.edenfs_path1}/buck-out' returned non-zero exit status 1. """, out, ) self.assert_results( fixer, num_problems=3, num_fixed_problems=1, num_failed_fixes=2 )
def test_end_to_end_test_with_various_scenarios(self, mock_watchman): side_effects: List[Dict[str, Any]] = [] calls = [] tmp_dir = tempfile.mkdtemp(prefix='eden_test.') try: # In edenfs_path1, we will break the snapshot check. edenfs_path1 = os.path.join(tmp_dir, 'path1') # In edenfs_path2, we will break the inotify check and the Nuclide # subscriptions check. edenfs_path2 = os.path.join(tmp_dir, 'path2') calls.append(call(['watch-list'])) side_effects.append({'roots': [edenfs_path1, edenfs_path2]}) calls.append(call(['watch-project', edenfs_path1])) side_effects.append({'watcher': 'eden'}) calls.append(call(['debug-get-subscriptions', edenfs_path1])) side_effects.append( _create_watchman_subscription( filewatcher_subscription=f'filewatcher-{edenfs_path1}', )) calls.append(call(['watch-project', edenfs_path2])) side_effects.append({'watcher': 'inotify'}) calls.append(call(['watch-del', edenfs_path2])) side_effects.append({'watch-del': True, 'root': edenfs_path2}) calls.append(call(['watch-project', edenfs_path2])) side_effects.append({'watcher': 'eden'}) calls.append(call(['debug-get-subscriptions', edenfs_path2])) side_effects.append( _create_watchman_subscription(filewatcher_subscription=None)) mock_watchman.side_effect = side_effects out = io.StringIO() dry_run = False mount_paths = OrderedDict() edenfs_path1_snapshot_hex = 'abcd' * 10 mount_paths[edenfs_path1] = { 'bind-mounts': {}, 'mount': edenfs_path1, 'scm_type': 'hg', 'snapshot': edenfs_path1_snapshot_hex, 'client-dir': '/I_DO_NOT_EXIST1' } mount_paths[edenfs_path2] = { 'bind-mounts': {}, 'mount': edenfs_path2, 'scm_type': 'git', 'snapshot': 'dcba' * 10, 'client-dir': '/I_DO_NOT_EXIST2' } config = FakeConfig(mount_paths, is_healthy=True) config.get_thrift_client()._mounts = [ eden_ttypes.MountInfo(mountPoint=edenfs_path1), eden_ttypes.MountInfo(mountPoint=edenfs_path2), ] os.mkdir(edenfs_path1) hg_dir = os.path.join(edenfs_path1, '.hg') os.mkdir(hg_dir) dirstate = os.path.join(hg_dir, 'dirstate') dirstate_hash = b'\x12\x34\x56\x78' * 5 parents = (dirstate_hash, b'\x00' * 20) with open(dirstate, 'wb') as f: eden.dirstate.write(f, parents, tuples_dict={}, copymap={}) mount_table = FakeMountTable() mount_table.stats[edenfs_path1] = mtab.MTStat(st_uid=os.getuid(), st_dev=11) mount_table.stats[edenfs_path2] = mtab.MTStat(st_uid=os.getuid(), st_dev=12) exit_code = doctor.cure_what_ails_you(config, dry_run, out, mount_table) finally: shutil.rmtree(tmp_dir) self.assertEqual( f'''\ Performing 3 checks for {edenfs_path1}. p1 for {edenfs_path1} is {'12345678' * 5}, but Eden's internal hash in its SNAPSHOT file is {edenfs_path1_snapshot_hex}. Performing 2 checks for {edenfs_path2}. Previous Watchman watcher for {edenfs_path2} was "inotify" but is now "eden". Nuclide appears to be used to edit {edenfs_path2}, but a key Watchman subscription appears to be missing. This can cause file changes to fail to show up in Nuclide. Currently, the only workaround this is to run "Nuclide Remote Projects: Kill And Restart" from the command palette in Atom. Number of fixes made: 1. Number of issues that could not be fixed: 2. ''', out.getvalue()) mock_watchman.assert_has_calls(calls) self.assertEqual(1, exit_code)