def _test_remount_checkouts( self, mock_get_roots_for_nuclide, mock_watchman, dry_run: bool, old_edenfs: bool = False, ) -> Tuple[int, str, List[Path]]: """Test that `eden doctor` remounts configured mount points that are not currently mounted. """ tmp_dir = self.make_temporary_directory() instance = FakeEdenInstance(tmp_dir) mounts = [] mount1 = instance.create_test_mount("path1") mounts.append(mount1.path) mounts.append(instance.create_test_mount("path2", active=False).path) if old_edenfs: # Mimic older versions of edenfs, and do not return mount state data. instance.get_thrift_client().change_mount_state(mount1.path, None) out = TestOutput() exit_code = doctor.cure_what_ails_you( typing.cast(EdenInstance, instance), dry_run, instance.mount_table, fs_util=FakeFsUtil(), process_finder=self.make_process_finder(), out=out, ) return exit_code, out.getvalue(), mounts
def test_edenfs_starting(self, mock_get_roots_for_nuclide, mock_watchman): instance = FakeEdenInstance( self.make_temporary_directory(), status=fb303_status.STARTING ) instance.create_test_mount("eden-mount") out = TestOutput() dry_run = False exit_code = doctor.cure_what_ails_you( instance, dry_run, FakeMountTable(), fs_util=FakeFsUtil(), process_finder=self.make_process_finder(), out=out, ) self.assertEqual( """\ <yellow>- Found problem:<reset> Eden is currently still starting. Please wait for edenfs to finish starting. If Eden seems to be taking too long to start you can try restarting it with "eden restart" <yellow>1 issue requires manual attention.<reset> Ask in the Eden Users group if you need help fixing issues with Eden: https://fb.facebook.com/groups/eden.users/ """, out.getvalue(), ) self.assertEqual(1, exit_code)
def test_unconfigured_mounts_dont_crash(self, mock_get_roots_for_nuclide): # If Eden advertises that a mount is active, but it is not in the # configuration, then at least don't throw an exception. instance = FakeEdenInstance(self.make_temporary_directory()) edenfs_path1 = instance.create_test_mount("path1").path edenfs_path2 = instance.create_test_mount("path2").path # Remove path2 from the list of mounts in the instance instance.remove_checkout_configuration(str(edenfs_path2)) dry_run = False out = TestOutput() exit_code = doctor.cure_what_ails_you( instance, dry_run, instance.mount_table, fs_util=FakeFsUtil(), process_finder=self.make_process_finder(), out=out, ) self.assertEqual( f"""\ Checking {edenfs_path1} Checking {edenfs_path2} <yellow>- Found problem:<reset> Checkout {edenfs_path2} is running but not listed in Eden's configuration file. Running "eden unmount {edenfs_path2}" will unmount this checkout. <yellow>1 issue requires manual attention.<reset> Ask in the Eden Users group if you need help fixing issues with Eden: https://fb.facebook.com/groups/eden.users/ """, out.getvalue(), ) self.assertEqual(1, exit_code)
def test_edenfs_stopping(self, mock_get_roots_for_nuclide, mock_watchman): instance = FakeEdenInstance( self.make_temporary_directory(), status=fb303_status.STOPPING ) instance.create_test_mount("eden-mount") out = TestOutput() dry_run = False exit_code = doctor.cure_what_ails_you( instance, dry_run, FakeMountTable(), fs_util=FakeFsUtil(), process_finder=self.make_process_finder(), out=out, ) self.assertEqual( """\ <yellow>- Found problem:<reset> Eden is currently shutting down. Either wait for edenfs to exit, or to forcibly kill Eden, run: eden stop --kill <yellow>1 issue requires manual attention.<reset> Ask in the Eden Users group if you need help fixing issues with Eden: https://fb.facebook.com/groups/eden.users/ """, out.getvalue(), ) self.assertEqual(1, exit_code)
def test_nfs_mounted(self, mock_is_nfs_mounted): mock_is_nfs_mounted.return_value = True instance = FakeEdenInstance(self.make_temporary_directory()) checkout = instance.create_test_mount("mount_dir") dry_run = True out = TestOutput() exit_code = doctor.cure_what_ails_you( instance, dry_run, instance.mount_table, fs_util=FakeFsUtil(), process_finder=self.make_process_finder(), out=out, ) expected = f"""\ <yellow>- Found problem:<reset> Eden's state directory is on an NFS file system: {instance.state_dir} This will likely cause performance problems and/or other errors. The most common cause for this is if your ~/local symlink does not point to local disk.\ Make sure that ~/local is a symlink pointing to local disk and then restart Eden. Checking {checkout.path} <yellow>- Found problem:<reset> The Mercurial data directory for {checkout.path}/.hg/sharedpath is at \ {instance.default_backing_repo}/.hg which is on a NFS filesystem. \ Accessing files and directories in this repository will be slow. <yellow>Discovered 2 problems during --dry-run<reset> """ self.assertEqual(expected, out.getvalue()) self.assertEqual(1, exit_code)
def cure_what_ails_you(self, dry_run: bool) -> TestOutput: out = TestOutput() doctor.cure_what_ails_you( typing.cast(EdenInstance, self.instance), dry_run, self.instance.mount_table, fs_util=FakeFsUtil(), process_finder=self.make_process_finder(), out=out, ) return out
def test_eden_not_in_use(self, mock_get_roots_for_nuclide, mock_watchman): instance = FakeEdenInstance(self.make_temporary_directory(), status=fb303_status.DEAD) out = TestOutput() dry_run = False exit_code = doctor.cure_what_ails_you( instance, dry_run, FakeMountTable(), fs_util=FakeFsUtil(), process_finder=self.make_process_finder(), out=out, ) self.assertEqual("Eden is not in use.\n", out.getvalue()) self.assertEqual(0, exit_code)
def run_varying_nfs(self, mock_path_read_text): instance = FakeEdenInstance(self.make_temporary_directory()) v = SimpleNamespace(mount_dir="mount_dir", shared_path="shared_path", instance=instance) mock_path_read_text.return_value = v.shared_path v.client_path = str(instance.create_test_mount(v.mount_dir).path) dry_run = True out = TestOutput() v.exit_code = doctor.cure_what_ails_you( instance, dry_run, instance.mount_table, fs_util=FakeFsUtil(), process_finder=self.make_process_finder(), out=out, ) v.stdout = out.getvalue() return v
def _test_with_pwd(self, instance: "FakeEdenInstance", pwd: Optional[str]) -> Tuple[int, str]: if pwd is None: old_pwd = os.environ.pop("PWD", None) else: old_pwd = os.environ.get("PWD") os.environ["PWD"] = pwd try: out = TestOutput() exit_code = doctor.cure_what_ails_you( typing.cast(EdenInstance, instance), dry_run=False, mount_table=instance.mount_table, fs_util=FakeFsUtil(), process_finder=self.make_process_finder(), out=out, ) return exit_code, out.getvalue() finally: if old_pwd is not None: os.environ["PWD"] = old_pwd
def test_not_all_mounts_have_watchman_watcher(self, mock_get_roots_for_nuclide, mock_watchman): instance = FakeEdenInstance(self.make_temporary_directory()) edenfs_path = str( instance.create_test_mount("eden-mount", scm_type="git").path) edenfs_path_not_watched = str( instance.create_test_mount("eden-mount-not-watched", scm_type="git").path) 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"}) mock_watchman.side_effect = side_effects out = TestOutput() dry_run = False exit_code = doctor.cure_what_ails_you( instance, dry_run, mount_table=instance.mount_table, fs_util=FakeFsUtil(), process_finder=self.make_process_finder(), out=out, ) self.assertEqual( f"Checking {edenfs_path}\n" f"Checking {edenfs_path_not_watched}\n" "<green>No issues detected.<reset>\n", out.getvalue(), ) mock_watchman.assert_has_calls(calls) self.assertEqual(0, exit_code)
def test_watchman_fails(self, mock_watchman): tmp_dir = self.make_temporary_directory() instance = FakeEdenInstance(tmp_dir) mount = instance.create_test_mount("path1", active=False).path # Make calls to watchman fail rather than returning expected output side_effects = [{"error": "watchman failed"}] mock_watchman.side_effect = side_effects out = TestOutput() exit_code = doctor.cure_what_ails_you( typing.cast(EdenInstance, instance), dry_run=False, mount_table=instance.mount_table, fs_util=FakeFsUtil(), process_finder=self.make_process_finder(), out=out, ) # "watchman watch-list" should have been called by the doctor code calls = [call(["watch-list"])] mock_watchman.assert_has_calls(calls) self.assertEqual( out.getvalue(), f"""\ Checking {mount} <yellow>- Found problem:<reset> {mount} is not currently mounted Remounting {mount}...<green>fixed<reset> <yellow>Successfully fixed 1 problem.<reset> """, ) self.assertEqual(exit_code, 0)
def test_end_to_end_test_with_various_scenarios( self, mock_get_roots_for_nuclide, mock_watchman ): side_effects: List[Dict[str, Any]] = [] calls = [] instance = FakeEdenInstance(self.make_temporary_directory()) # In edenfs_path1, we will break the snapshot check. edenfs_path1_snapshot = "abcd" * 10 edenfs_path1_dirstate_parent = "12345678" * 5 edenfs_path1 = str( instance.create_test_mount( "path1", snapshot=edenfs_path1_snapshot, dirstate_parent=edenfs_path1_dirstate_parent, ).path ) # In edenfs_path2, we will break the inotify check and the Nuclide # subscriptions check. edenfs_path2 = str( instance.create_test_mount("path2", scm_type="git", setup_path=False).path ) # In edenfs_path3, we do not create the .hg directory edenfs_path3 = str(instance.create_test_mount("path3", setup_path=False).path) os.makedirs(edenfs_path3) # Assume all paths are used as root folders in a connected Nuclide. mock_get_roots_for_nuclide.return_value = { edenfs_path1, edenfs_path2, edenfs_path3, } calls.append(call(["watch-list"])) side_effects.append({"roots": [edenfs_path1, edenfs_path2, edenfs_path3]}) 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_subscriptions=[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_subscriptions=[])) calls.append(call(["watch-project", edenfs_path3])) side_effects.append({"watcher": "eden"}) calls.append(call(["debug-get-subscriptions", edenfs_path3])) side_effects.append( _create_watchman_subscription( filewatcher_subscriptions=[f"filewatcher-{edenfs_path3}"] ) ) mock_watchman.side_effect = side_effects out = TestOutput() dry_run = False exit_code = doctor.cure_what_ails_you( instance, dry_run, instance.mount_table, fs_util=FakeFsUtil(), process_finder=self.make_process_finder(), out=out, ) self.assertEqual( f"""\ Checking {edenfs_path1} <yellow>- Found problem:<reset> Found inconsistent/missing data in {edenfs_path1}/.hg: mercurial's parent commit is {edenfs_path1_dirstate_parent}, \ but Eden's internal parent commit is {edenfs_path1_snapshot} Repairing hg directory contents for {edenfs_path1}...<green>fixed<reset> Checking {edenfs_path2} <yellow>- Found problem:<reset> Watchman is watching {edenfs_path2} with the wrong watcher type: \ "inotify" instead of "eden" Fixing watchman watch for {edenfs_path2}...<green>fixed<reset> <yellow>- Found problem:<reset> Nuclide appears to be used to edit the following directories under {edenfs_path2}: {edenfs_path2} but the following Watchman subscriptions appear to be missing: filewatcher-{edenfs_path2} This can cause file changes to fail to show up in Nuclide. Currently, the only workaround for this is to run "Nuclide Remote Projects: Kill And Restart" from the command palette in Atom. Checking {edenfs_path3} <yellow>- Found problem:<reset> Missing hg directory: {edenfs_path3}/.hg Repairing hg directory contents for {edenfs_path3}...<green>fixed<reset> <yellow>Successfully fixed 3 problems.<reset> <yellow>1 issue requires manual attention.<reset> Ask in the Eden Users group if you need help fixing issues with Eden: https://fb.facebook.com/groups/eden.users/ """, out.getvalue(), ) mock_watchman.assert_has_calls(calls) self.assertEqual(1, exit_code)
def __init__(self, *args, **kw): super().__init__(*args, **kw) self.fs_util = FakeFsUtil()